CachedRowSetWriter.java revision 11099:678faa7d1a6a
1/*
2 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.rowset.internal;
27
28import java.sql.*;
29import javax.sql.*;
30import java.util.*;
31import java.io.*;
32import sun.reflect.misc.ReflectUtil;
33
34import com.sun.rowset.*;
35import java.text.MessageFormat;
36import javax.sql.rowset.*;
37import javax.sql.rowset.serial.SQLInputImpl;
38import javax.sql.rowset.serial.SerialArray;
39import javax.sql.rowset.serial.SerialBlob;
40import javax.sql.rowset.serial.SerialClob;
41import javax.sql.rowset.serial.SerialStruct;
42import javax.sql.rowset.spi.*;
43
44
45/**
46 * The facility called on internally by the <code>RIOptimisticProvider</code> implementation to
47 * propagate changes back to the data source from which the rowset got its data.
48 * <P>
49 * A <code>CachedRowSetWriter</code> object, called a writer, has the public
50 * method <code>writeData</code> for writing modified data to the underlying data source.
51 * This method is invoked by the rowset internally and is never invoked directly by an application.
52 * A writer also has public methods for setting and getting
53 * the <code>CachedRowSetReader</code> object, called a reader, that is associated
54 * with the writer. The remainder of the methods in this class are private and
55 * are invoked internally, either directly or indirectly, by the method
56 * <code>writeData</code>.
57 * <P>
58 * Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and
59 * the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects.
60 * Standard JDBC RowSet implementations provide an object instance of this
61 * writer by invoking the <code>SyncProvider.getRowSetWriter()</code> method.
62 *
63 * @version 0.2
64 * @author Jonathan Bruce
65 * @see javax.sql.rowset.spi.SyncProvider
66 * @see javax.sql.rowset.spi.SyncFactory
67 * @see javax.sql.rowset.spi.SyncFactoryException
68 */
69public class CachedRowSetWriter implements TransactionalWriter, Serializable {
70
71/**
72 * The <code>Connection</code> object that this writer will use to make a
73 * connection to the data source to which it will write data.
74 *
75 */
76    private transient Connection con;
77
78/**
79 * The SQL <code>SELECT</code> command that this writer will call
80 * internally. The method <code>initSQLStatements</code> builds this
81 * command by supplying the words "SELECT" and "FROM," and using
82 * metadata to get the table name and column names .
83 *
84 * @serial
85 */
86    private String selectCmd;
87
88/**
89 * The SQL <code>UPDATE</code> command that this writer will call
90 * internally to write data to the rowset's underlying data source.
91 * The method <code>initSQLStatements</code> builds this <code>String</code>
92 * object.
93 *
94 * @serial
95 */
96    private String updateCmd;
97
98/**
99 * The SQL <code>WHERE</code> clause the writer will use for update
100 * statements in the <code>PreparedStatement</code> object
101 * it sends to the underlying data source.
102 *
103 * @serial
104 */
105    private String updateWhere;
106
107/**
108 * The SQL <code>DELETE</code> command that this writer will call
109 * internally to delete a row in the rowset's underlying data source.
110 *
111 * @serial
112 */
113    private String deleteCmd;
114
115/**
116 * The SQL <code>WHERE</code> clause the writer will use for delete
117 * statements in the <code>PreparedStatement</code> object
118 * it sends to the underlying data source.
119 *
120 * @serial
121 */
122    private String deleteWhere;
123
124/**
125 * The SQL <code>INSERT INTO</code> command that this writer will internally use
126 * to insert data into the rowset's underlying data source.  The method
127 * <code>initSQLStatements</code> builds this command with a question
128 * mark parameter placeholder for each column in the rowset.
129 *
130 * @serial
131 */
132    private String insertCmd;
133
134/**
135 * An array containing the column numbers of the columns that are
136 * needed to uniquely identify a row in the <code>CachedRowSet</code> object
137 * for which this <code>CachedRowSetWriter</code> object is the writer.
138 *
139 * @serial
140 */
141    private int[] keyCols;
142
143/**
144 * An array of the parameters that should be used to set the parameter
145 * placeholders in a <code>PreparedStatement</code> object that this
146 * writer will execute.
147 *
148 * @serial
149 */
150    private Object[] params;
151
152/**
153 * The <code>CachedRowSetReader</code> object that has been
154 * set as the reader for the <code>CachedRowSet</code> object
155 * for which this <code>CachedRowSetWriter</code> object is the writer.
156 *
157 * @serial
158 */
159    private CachedRowSetReader reader;
160
161/**
162 * The <code>ResultSetMetaData</code> object that contains information
163 * about the columns in the <code>CachedRowSet</code> object
164 * for which this <code>CachedRowSetWriter</code> object is the writer.
165 *
166 * @serial
167 */
168    private ResultSetMetaData callerMd;
169
170/**
171 * The number of columns in the <code>CachedRowSet</code> object
172 * for which this <code>CachedRowSetWriter</code> object is the writer.
173 *
174 * @serial
175 */
176    private int callerColumnCount;
177
178/**
179 * This <code>CachedRowSet<code> will hold the conflicting values
180 *  retrieved from the db and hold it.
181 */
182    private CachedRowSetImpl crsResolve;
183
184/**
185 * This <code>ArrayList<code> will hold the values of SyncResolver.*
186 */
187    private ArrayList<Integer> status;
188
189/**
190 * This will check whether the same field value has changed both
191 * in database and CachedRowSet.
192 */
193    private int iChangedValsInDbAndCRS;
194
195/**
196 * This will hold the number of cols for which the values have
197 * changed only in database.
198 */
199    private int iChangedValsinDbOnly ;
200
201    private JdbcRowSetResourceBundle resBundle;
202
203    public CachedRowSetWriter() {
204       try {
205               resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
206       } catch(IOException ioe) {
207               throw new RuntimeException(ioe);
208       }
209    }
210
211/**
212 * Propagates changes in the given <code>RowSet</code> object
213 * back to its underlying data source and returns <code>true</code>
214 * if successful. The writer will check to see if
215 * the data in the pre-modified rowset (the original values) differ
216 * from the data in the underlying data source.  If data in the data
217 * source has been modified by someone else, there is a conflict,
218 * and in that case, the writer will not write to the data source.
219 * In other words, the writer uses an optimistic concurrency algorithm:
220 * It checks for conflicts before making changes rather than restricting
221 * access for concurrent users.
222 * <P>
223 * This method is called by the rowset internally when
224 * the application invokes the method <code>acceptChanges</code>.
225 * The <code>writeData</code> method in turn calls private methods that
226 * it defines internally.
227 * The following is a general summary of what the method
228 * <code>writeData</code> does, much of which is accomplished
229 * through calls to its own internal methods.
230 * <OL>
231 * <LI>Creates a <code>CachedRowSet</code> object from the given
232 *     <code>RowSet</code> object
233 * <LI>Makes a connection with the data source
234 *   <UL>
235 *      <LI>Disables autocommit mode if it is not already disabled
236 *      <LI>Sets the transaction isolation level to that of the rowset
237 *   </UL>
238 * <LI>Checks to see if the reader has read new data since the writer
239 *     was last called and, if so, calls the method
240 *    <code>initSQLStatements</code> to initialize new SQL statements
241 *   <UL>
242 *       <LI>Builds new <code>SELECT</code>, <code>UPDATE</code>,
243 *           <code>INSERT</code>, and <code>DELETE</code> statements
244 *       <LI>Uses the <code>CachedRowSet</code> object's metadata to
245 *           determine the table name, column names, and the columns
246 *           that make up the primary key
247 *   </UL>
248 * <LI>When there is no conflict, propagates changes made to the
249 *     <code>CachedRowSet</code> object back to its underlying data source
250 *   <UL>
251 *      <LI>Iterates through each row of the <code>CachedRowSet</code> object
252 *          to determine whether it has been updated, inserted, or deleted
253 *      <LI>If the corresponding row in the data source has not been changed
254 *          since the rowset last read its
255 *          values, the writer will use the appropriate command to update,
256 *          insert, or delete the row
257 *      <LI>If any data in the data source does not match the original values
258 *          for the <code>CachedRowSet</code> object, the writer will roll
259 *          back any changes it has made to the row in the data source.
260 *   </UL>
261 * </OL>
262 *
263 * @return <code>true</code> if changes to the rowset were successfully
264 *         written to the rowset's underlying data source;
265 *         <code>false</code> otherwise
266 */
267    public boolean writeData(RowSetInternal caller) throws SQLException {
268        long conflicts = 0;
269        boolean showDel = false;
270        PreparedStatement pstmtIns = null;
271        iChangedValsInDbAndCRS = 0;
272        iChangedValsinDbOnly = 0;
273
274        // We assume caller is a CachedRowSet
275        CachedRowSetImpl crs = (CachedRowSetImpl)caller;
276        // crsResolve = new CachedRowSetImpl();
277        this.crsResolve = new CachedRowSetImpl();;
278
279        // The reader is registered with the writer at design time.
280        // This is not required, in general.  The reader has logic
281        // to get a JDBC connection, so call it.
282
283        con = reader.connect(caller);
284
285
286        if (con == null) {
287            throw new SQLException(resBundle.handleGetObject("crswriter.connect").toString());
288        }
289
290        /*
291         // Fix 6200646.
292         // Don't change the connection or transaction properties. This will fail in a
293         // J2EE container.
294        if (con.getAutoCommit() == true)  {
295            con.setAutoCommit(false);
296        }
297
298        con.setTransactionIsolation(crs.getTransactionIsolation());
299        */
300
301        initSQLStatements(crs);
302        int iColCount;
303
304        RowSetMetaDataImpl rsmdWrite = (RowSetMetaDataImpl)crs.getMetaData();
305        RowSetMetaDataImpl rsmdResolv = new RowSetMetaDataImpl();
306
307        iColCount = rsmdWrite.getColumnCount();
308        int sz= crs.size()+1;
309        status = new ArrayList<>(sz);
310
311        status.add(0,null);
312        rsmdResolv.setColumnCount(iColCount);
313
314        for(int i =1; i <= iColCount; i++) {
315            rsmdResolv.setColumnType(i, rsmdWrite.getColumnType(i));
316            rsmdResolv.setColumnName(i, rsmdWrite.getColumnName(i));
317            rsmdResolv.setNullable(i, ResultSetMetaData.columnNullableUnknown);
318        }
319        this.crsResolve.setMetaData(rsmdResolv);
320
321        // moved outside the insert inner loop
322        //pstmtIns = con.prepareStatement(insertCmd);
323
324        if (callerColumnCount < 1) {
325            // No data, so return success.
326            if (reader.getCloseConnection() == true)
327                    con.close();
328            return true;
329        }
330        // We need to see rows marked for deletion.
331        showDel = crs.getShowDeleted();
332        crs.setShowDeleted(true);
333
334        // Look at all the rows.
335        crs.beforeFirst();
336
337        int rows =1;
338        while (crs.next()) {
339            if (crs.rowDeleted()) {
340                // The row has been deleted.
341                if (deleteOriginalRow(crs, this.crsResolve)) {
342                       status.add(rows, SyncResolver.DELETE_ROW_CONFLICT);
343                       conflicts++;
344                } else {
345                      // delete happened without any occurrence of conflicts
346                      // so update status accordingly
347                       status.add(rows, SyncResolver.NO_ROW_CONFLICT);
348                }
349
350           } else if (crs.rowInserted()) {
351                // The row has been inserted.
352
353                pstmtIns = con.prepareStatement(insertCmd);
354                if (insertNewRow(crs, pstmtIns, this.crsResolve)) {
355                          status.add(rows, SyncResolver.INSERT_ROW_CONFLICT);
356                          conflicts++;
357                } else {
358                      // insert happened without any occurrence of conflicts
359                      // so update status accordingly
360                       status.add(rows, SyncResolver.NO_ROW_CONFLICT);
361                }
362            } else  if (crs.rowUpdated()) {
363                  // The row has been updated.
364                       if (updateOriginalRow(crs)) {
365                             status.add(rows, SyncResolver.UPDATE_ROW_CONFLICT);
366                             conflicts++;
367               } else {
368                      // update happened without any occurrence of conflicts
369                      // so update status accordingly
370                      status.add(rows, SyncResolver.NO_ROW_CONFLICT);
371               }
372
373            } else {
374               /** The row is neither of inserted, updated or deleted.
375                *  So set nulls in the this.crsResolve for this row,
376                *  as nothing is to be done for such rows.
377                *  Also note that if such a row has been changed in database
378                *  and we have not changed(inserted, updated or deleted)
379                *  that is fine.
380                **/
381                int icolCount = crs.getMetaData().getColumnCount();
382                status.add(rows, SyncResolver.NO_ROW_CONFLICT);
383
384                this.crsResolve.moveToInsertRow();
385                for(int cols=0;cols<iColCount;cols++) {
386                   this.crsResolve.updateNull(cols+1);
387                } //end for
388
389                this.crsResolve.insertRow();
390                this.crsResolve.moveToCurrentRow();
391
392                } //end if
393         rows++;
394      } //end while
395
396        // close the insert statement
397        if(pstmtIns!=null)
398        pstmtIns.close();
399        // reset
400        crs.setShowDeleted(showDel);
401
402        crs.beforeFirst();
403        this.crsResolve.beforeFirst();
404
405    if(conflicts != 0) {
406        SyncProviderException spe = new SyncProviderException(conflicts + " " +
407                resBundle.handleGetObject("crswriter.conflictsno").toString());
408        //SyncResolver syncRes = spe.getSyncResolver();
409
410         SyncResolverImpl syncResImpl = (SyncResolverImpl) spe.getSyncResolver();
411
412         syncResImpl.setCachedRowSet(crs);
413         syncResImpl.setCachedRowSetResolver(this.crsResolve);
414
415         syncResImpl.setStatus(status);
416         syncResImpl.setCachedRowSetWriter(this);
417
418        throw spe;
419    } else {
420         return true;
421    }
422       /*
423       if (conflict == true) {
424            con.rollback();
425            return false;
426        } else {
427            con.commit();
428                if (reader.getCloseConnection() == true) {
429                       con.close();
430                }
431            return true;
432        }
433        */
434
435  } //end writeData
436
437/**
438 * Updates the given <code>CachedRowSet</code> object's underlying data
439 * source so that updates to the rowset are reflected in the original
440 * data source, and returns <code>false</code> if the update was successful.
441 * A return value of <code>true</code> indicates that there is a conflict,
442 * meaning that a value updated in the rowset has already been changed by
443 * someone else in the underlying data source.  A conflict can also exist
444 * if, for example, more than one row in the data source would be affected
445 * by the update or if no rows would be affected.  In any case, if there is
446 * a conflict, this method does not update the underlying data source.
447 * <P>
448 * This method is called internally by the method <code>writeData</code>
449 * if a row in the <code>CachedRowSet</code> object for which this
450 * <code>CachedRowSetWriter</code> object is the writer has been updated.
451 *
452 * @return <code>false</code> if the update to the underlying data source is
453 *         successful; <code>true</code> otherwise
454 * @throws SQLException if a database access error occurs
455 */
456    private boolean updateOriginalRow(CachedRowSet crs)
457        throws SQLException {
458        PreparedStatement pstmt;
459        int i = 0;
460        int idx = 0;
461
462        // Select the row from the database.
463        ResultSet origVals = crs.getOriginalRow();
464        origVals.next();
465
466        try {
467            updateWhere = buildWhereClause(updateWhere, origVals);
468
469
470             /**
471              *  The following block of code is for checking a particular type of
472              *  query where in there is a where clause. Without this block, if a
473              *  SQL statement is built the "where" clause will appear twice hence
474              *  the DB errors out and a SQLException is thrown. This code also
475              *  considers that the where clause is in the right place as the
476              *  CachedRowSet object would already have been populated with this
477              *  query before coming to this point.
478              **/
479
480
481            String tempselectCmd = selectCmd.toLowerCase();
482
483            int idxWhere = tempselectCmd.indexOf("where");
484
485            if(idxWhere != -1)
486            {
487               String tempSelect = selectCmd.substring(0,idxWhere);
488               selectCmd = tempSelect;
489            }
490
491            pstmt = con.prepareStatement(selectCmd + updateWhere,
492                        ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
493
494            for (i = 0; i < keyCols.length; i++) {
495                if (params[i] != null) {
496                    pstmt.setObject(++idx, params[i]);
497                } else {
498                    continue;
499                }
500            }
501
502            try {
503                pstmt.setMaxRows(crs.getMaxRows());
504                pstmt.setMaxFieldSize(crs.getMaxFieldSize());
505                pstmt.setEscapeProcessing(crs.getEscapeProcessing());
506                pstmt.setQueryTimeout(crs.getQueryTimeout());
507            } catch (Exception ex) {
508                // Older driver don't support these operations.
509            }
510
511            ResultSet rs = null;
512            rs = pstmt.executeQuery();
513            ResultSetMetaData rsmd = rs.getMetaData();
514
515            if (rs.next()) {
516                if (rs.next()) {
517                   /** More than one row conflict.
518                    *  If rs has only one row we are able to
519                    *  uniquely identify the row where update
520                    *  have to happen else if more than one
521                    *  row implies we cannot uniquely identify the row
522                    *  where we have to do updates.
523                    *  crs.setKeyColumns needs to be set to
524                    *  come out of this situation.
525                    */
526
527                   return true;
528                }
529
530                // don't close the rs
531                // we require the record in rs to be used.
532                // rs.close();
533                // pstmt.close();
534                rs.first();
535
536                // how many fields need to be updated
537                int colsNotChanged = 0;
538                Vector<Integer> cols = new Vector<>();
539                String updateExec = updateCmd;
540                Object orig;
541                Object curr;
542                Object rsval;
543                boolean boolNull = true;
544                Object objVal = null;
545
546                // There's only one row and the cursor
547                // needs to be on that row.
548
549                boolean first = true;
550                boolean flag = true;
551
552          this.crsResolve.moveToInsertRow();
553
554          for (i = 1; i <= callerColumnCount; i++) {
555                orig = origVals.getObject(i);
556                curr = crs.getObject(i);
557                rsval = rs.getObject(i);
558                /*
559                 * the following block creates equivalent objects
560                 * that would have been created if this rs is populated
561                 * into a CachedRowSet so that comparison of the column values
562                 * from the ResultSet and CachedRowSet are possible
563                 */
564                Map<String, Class<?>> map = (crs.getTypeMap() == null)?con.getTypeMap():crs.getTypeMap();
565                if (rsval instanceof Struct) {
566
567                    Struct s = (Struct)rsval;
568
569                    // look up the class in the map
570                    Class<?> c = null;
571                    c = map.get(s.getSQLTypeName());
572                    if (c != null) {
573                        // create new instance of the class
574                        SQLData obj = null;
575                        try {
576                            obj = (SQLData)ReflectUtil.newInstance(c);
577                        } catch (Exception ex) {
578                            throw new SQLException("Unable to Instantiate: ", ex);
579                        }
580                        // get the attributes from the struct
581                        Object attribs[] = s.getAttributes(map);
582                        // create the SQLInput "stream"
583                        SQLInputImpl sqlInput = new SQLInputImpl(attribs, map);
584                        // read the values...
585                        obj.readSQL(sqlInput, s.getSQLTypeName());
586                        rsval = obj;
587                    }
588                } else if (rsval instanceof SQLData) {
589                    rsval = new SerialStruct((SQLData)rsval, map);
590                } else if (rsval instanceof Blob) {
591                    rsval = new SerialBlob((Blob)rsval);
592                } else if (rsval instanceof Clob) {
593                    rsval = new SerialClob((Clob)rsval);
594                } else if (rsval instanceof java.sql.Array) {
595                    rsval = new SerialArray((java.sql.Array)rsval, map);
596                }
597
598                // reset boolNull if it had been set
599                boolNull = true;
600
601                /** This addtional checking has been added when the current value
602                 *  in the DB is null, but the DB had a different value when the
603                 *  data was actaully fetched into the CachedRowSet.
604                 **/
605
606                if(rsval == null && orig != null) {
607                   // value in db has changed
608                    // don't proceed with synchronization
609                    // get the value in db and pass it to the resolver.
610
611                    iChangedValsinDbOnly++;
612                   // Set the boolNull to false,
613                   // in order to set the actual value;
614                     boolNull = false;
615                     objVal = rsval;
616                }
617
618                /** Adding the checking for rsval to be "not" null or else
619                 *  it would through a NullPointerException when the values
620                 *  are compared.
621                 **/
622
623                else if(rsval != null && (!rsval.equals(orig)))
624                {
625                    // value in db has changed
626                    // don't proceed with synchronization
627                    // get the value in db and pass it to the resolver.
628
629                    iChangedValsinDbOnly++;
630                   // Set the boolNull to false,
631                   // in order to set the actual value;
632                     boolNull = false;
633                     objVal = rsval;
634                } else if (  (orig == null || curr == null) ) {
635
636                        /** Adding the additonal condition of checking for "flag"
637                         *  boolean variable, which would otherwise result in
638                         *  building a invalid query, as the comma would not be
639                         *  added to the query string.
640                         **/
641
642                        if (first == false || flag == false) {
643                          updateExec += ", ";
644                         }
645                        updateExec += crs.getMetaData().getColumnName(i);
646                        cols.add(i);
647                        updateExec += " = ? ";
648                        first = false;
649
650                /** Adding the extra condition for orig to be "not" null as the
651                 *  condition for orig to be null is take prior to this, if this
652                 *  is not added it will result in a NullPointerException when
653                 *  the values are compared.
654                 **/
655
656                }  else if (orig.equals(curr)) {
657                       colsNotChanged++;
658                     //nothing to update in this case since values are equal
659
660                /** Adding the extra condition for orig to be "not" null as the
661                 *  condition for orig to be null is take prior to this, if this
662                 *  is not added it will result in a NullPointerException when
663                 *  the values are compared.
664                 **/
665
666                } else if(orig.equals(curr) == false) {
667                      // When values from db and values in CachedRowSet are not equal,
668                      // if db value is same as before updation for each col in
669                      // the row before fetching into CachedRowSet,
670                      // only then we go ahead with updation, else we
671                      // throw SyncProviderException.
672
673                      // if value has changed in db after fetching from db
674                      // for some cols of the row and at the same time, some other cols
675                      // have changed in CachedRowSet, no synchronization happens
676
677                      // Synchronization happens only when data when fetching is
678                      // same or at most has changed in cachedrowset
679
680                      // check orig value with what is there in crs for a column
681                      // before updation in crs.
682
683                         if(crs.columnUpdated(i)) {
684                             if(rsval.equals(orig)) {
685                               // At this point we are sure that
686                               // the value updated in crs was from
687                               // what is in db now and has not changed
688                                 if (flag == false || first == false) {
689                                    updateExec += ", ";
690                                 }
691                                updateExec += crs.getMetaData().getColumnName(i);
692                                cols.add(i);
693                                updateExec += " = ? ";
694                                flag = false;
695                             } else {
696                               // Here the value has changed in the db after
697                               // data was fetched
698                               // Plus store this row from CachedRowSet and keep it
699                               // in a new CachedRowSet
700                               boolNull= false;
701                               objVal = rsval;
702                               iChangedValsInDbAndCRS++;
703                             }
704                         }
705                  }
706
707                    if(!boolNull) {
708                        this.crsResolve.updateObject(i,objVal);
709                                 } else {
710                                      this.crsResolve.updateNull(i);
711                                 }
712                } //end for
713
714                rs.close();
715                pstmt.close();
716
717               this.crsResolve.insertRow();
718                   this.crsResolve.moveToCurrentRow();
719
720                /**
721                 * if nothing has changed return now - this can happen
722                 * if column is updated to the same value.
723                 * if colsNotChanged == callerColumnCount implies we are updating
724                 * the database with ALL COLUMNS HAVING SAME VALUES,
725                 * so skip going to database, else do as usual.
726                 **/
727                if ( (first == false && cols.size() == 0)  ||
728                     colsNotChanged == callerColumnCount ) {
729                    return false;
730                }
731
732                if(iChangedValsInDbAndCRS != 0 || iChangedValsinDbOnly != 0) {
733                   return true;
734                }
735
736
737                updateExec += updateWhere;
738
739                pstmt = con.prepareStatement(updateExec);
740
741                // Comments needed here
742                for (i = 0; i < cols.size(); i++) {
743                    Object obj = crs.getObject(cols.get(i));
744                    if (obj != null)
745                        pstmt.setObject(i + 1, obj);
746                    else
747                        pstmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));
748                }
749                idx = i;
750
751                // Comments needed here
752                for (i = 0; i < keyCols.length; i++) {
753                    if (params[i] != null) {
754                        pstmt.setObject(++idx, params[i]);
755                    } else {
756                        continue;
757                    }
758                }
759
760                i = pstmt.executeUpdate();
761
762               /**
763                * i should be equal to 1(row count), because we update
764                * one row(returned as row count) at a time, if all goes well.
765                * if 1 != 1, this implies we have not been able to
766                * do updations properly i.e there is a conflict in database
767                * versus what is in CachedRowSet for this particular row.
768                **/
769
770                 return false;
771
772            } else {
773                /**
774                 * Cursor will be here, if the ResultSet may not return even a single row
775                 * i.e. we can't find the row where to update because it has been deleted
776                 * etc. from the db.
777                 * Present the whole row as null to user, to force null to be sync'ed
778                 * and hence nothing to be synced.
779                 *
780                 * NOTE:
781                 * ------
782                 * In the database if a column that is mapped to java.sql.Types.REAL stores
783                 * a Double value and is compared with value got from ResultSet.getFloat()
784                 * no row is retrieved and will throw a SyncProviderException. For details
785                 * see bug Id 5053830
786                 **/
787                return true;
788            }
789        } catch (SQLException ex) {
790            ex.printStackTrace();
791            // if executeUpdate fails it will come here,
792            // update crsResolve with null rows
793            this.crsResolve.moveToInsertRow();
794
795            for(i = 1; i <= callerColumnCount; i++) {
796               this.crsResolve.updateNull(i);
797            }
798
799            this.crsResolve.insertRow();
800            this.crsResolve.moveToCurrentRow();
801
802            return true;
803        }
804    }
805
806   /**
807    * Inserts a row that has been inserted into the given
808    * <code>CachedRowSet</code> object into the data source from which
809    * the rowset is derived, returning <code>false</code> if the insertion
810    * was successful.
811    *
812    * @param crs the <code>CachedRowSet</code> object that has had a row inserted
813    *            and to whose underlying data source the row will be inserted
814    * @param pstmt the <code>PreparedStatement</code> object that will be used
815    *              to execute the insertion
816    * @return <code>false</code> to indicate that the insertion was successful;
817    *         <code>true</code> otherwise
818    * @throws SQLException if a database access error occurs
819    */
820   private boolean insertNewRow(CachedRowSet crs,
821       PreparedStatement pstmt, CachedRowSetImpl crsRes) throws SQLException {
822
823       boolean returnVal = false;
824
825       try (PreparedStatement pstmtSel = con.prepareStatement(selectCmd,
826                       ResultSet.TYPE_SCROLL_SENSITIVE,
827                       ResultSet.CONCUR_READ_ONLY);
828            ResultSet rs = pstmtSel.executeQuery();
829            ResultSet rs2 = con.getMetaData().getPrimaryKeys(null, null,
830                       crs.getTableName())
831       ) {
832
833           ResultSetMetaData rsmd = crs.getMetaData();
834           int icolCount = rsmd.getColumnCount();
835           String[] primaryKeys = new String[icolCount];
836           int k = 0;
837           while (rs2.next()) {
838               primaryKeys[k] = rs2.getString("COLUMN_NAME");
839               k++;
840           }
841
842           if (rs.next()) {
843               for (String pkName : primaryKeys) {
844                   if (!isPKNameValid(pkName, rsmd)) {
845
846                       /* We came here as one of the primary keys
847                        * of the table is not present in the cached
848                        * rowset object, it should be an autoincrement column
849                        * and not included while creating CachedRowSet
850                        * Object, proceed to check for other primary keys
851                        */
852                       continue;
853                   }
854
855                   Object crsPK = crs.getObject(pkName);
856                   if (crsPK == null) {
857                       /*
858                        * It is possible that the PK is null on some databases
859                        * and will be filled in at insert time (MySQL for example)
860                        */
861                       break;
862                   }
863
864                   String rsPK = rs.getObject(pkName).toString();
865                   if (crsPK.toString().equals(rsPK)) {
866                       returnVal = true;
867                       this.crsResolve.moveToInsertRow();
868                       for (int i = 1; i <= icolCount; i++) {
869                           String colname = (rs.getMetaData()).getColumnName(i);
870                           if (colname.equals(pkName))
871                               this.crsResolve.updateObject(i,rsPK);
872                           else
873                               this.crsResolve.updateNull(i);
874                       }
875                       this.crsResolve.insertRow();
876                       this.crsResolve.moveToCurrentRow();
877                   }
878               }
879           }
880
881           if (returnVal) {
882               return returnVal;
883           }
884
885           try {
886               for (int i = 1; i <= icolCount; i++) {
887                   Object obj = crs.getObject(i);
888                   if (obj != null) {
889                       pstmt.setObject(i, obj);
890                   } else {
891                       pstmt.setNull(i,crs.getMetaData().getColumnType(i));
892                   }
893               }
894
895               pstmt.executeUpdate();
896               return false;
897
898           } catch (SQLException ex) {
899               /*
900                * Cursor will come here if executeUpdate fails.
901                * There can be many reasons why the insertion failed,
902                * one can be violation of primary key.
903                * Hence we cannot exactly identify why the insertion failed,
904                * present the current row as a null row to the caller.
905                */
906               this.crsResolve.moveToInsertRow();
907
908               for (int i = 1; i <= icolCount; i++) {
909                   this.crsResolve.updateNull(i);
910               }
911
912               this.crsResolve.insertRow();
913               this.crsResolve.moveToCurrentRow();
914
915               return true;
916           }
917       }
918   }
919
920/**
921 * Deletes the row in the underlying data source that corresponds to
922 * a row that has been deleted in the given <code> CachedRowSet</code> object
923 * and returns <code>false</code> if the deletion was successful.
924 * <P>
925 * This method is called internally by this writer's <code>writeData</code>
926 * method when a row in the rowset has been deleted. The values in the
927 * deleted row are the same as those that are stored in the original row
928 * of the given <code>CachedRowSet</code> object.  If the values in the
929 * original row differ from the row in the underlying data source, the row
930 * in the data source is not deleted, and <code>deleteOriginalRow</code>
931 * returns <code>true</code> to indicate that there was a conflict.
932 *
933 *
934 * @return <code>false</code> if the deletion was successful, which means that
935 *         there was no conflict; <code>true</code> otherwise
936 * @throws SQLException if there was a database access error
937 */
938    private boolean deleteOriginalRow(CachedRowSet crs, CachedRowSetImpl crsRes) throws SQLException {
939        PreparedStatement pstmt;
940        int i;
941        int idx = 0;
942        String strSelect;
943    // Select the row from the database.
944        ResultSet origVals = crs.getOriginalRow();
945        origVals.next();
946
947        deleteWhere = buildWhereClause(deleteWhere, origVals);
948        pstmt = con.prepareStatement(selectCmd + deleteWhere,
949                ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
950
951        for (i = 0; i < keyCols.length; i++) {
952            if (params[i] != null) {
953                pstmt.setObject(++idx, params[i]);
954            } else {
955                continue;
956            }
957        }
958
959        try {
960            pstmt.setMaxRows(crs.getMaxRows());
961            pstmt.setMaxFieldSize(crs.getMaxFieldSize());
962            pstmt.setEscapeProcessing(crs.getEscapeProcessing());
963            pstmt.setQueryTimeout(crs.getQueryTimeout());
964        } catch (Exception ex) {
965            /*
966             * Older driver don't support these operations...
967             */
968            ;
969        }
970
971        ResultSet rs = pstmt.executeQuery();
972
973        if (rs.next() == true) {
974            if (rs.next()) {
975                // more than one row
976                return true;
977            }
978            rs.first();
979
980            // Now check all the values in rs to be same in
981            // db also before actually going ahead with deleting
982            boolean boolChanged = false;
983
984            crsRes.moveToInsertRow();
985
986            for (i = 1; i <= crs.getMetaData().getColumnCount(); i++) {
987
988                Object original = origVals.getObject(i);
989                Object changed = rs.getObject(i);
990
991                if(original != null && changed != null ) {
992                  if(! (original.toString()).equals(changed.toString()) ) {
993                      boolChanged = true;
994                      crsRes.updateObject(i,origVals.getObject(i));
995                  }
996                } else {
997                   crsRes.updateNull(i);
998               }
999            }
1000
1001           crsRes.insertRow();
1002           crsRes.moveToCurrentRow();
1003
1004           if(boolChanged) {
1005               // do not delete as values in db have changed
1006               // deletion will not happen for this row from db
1007                   // exit now returning true. i.e. conflict
1008               return true;
1009            } else {
1010                // delete the row.
1011                // Go ahead with deleting,
1012                // don't do anything here
1013            }
1014
1015            String cmd = deleteCmd + deleteWhere;
1016            pstmt = con.prepareStatement(cmd);
1017
1018            idx = 0;
1019            for (i = 0; i < keyCols.length; i++) {
1020                if (params[i] != null) {
1021                    pstmt.setObject(++idx, params[i]);
1022                } else {
1023                    continue;
1024                }
1025            }
1026
1027            if (pstmt.executeUpdate() != 1) {
1028                return true;
1029            }
1030            pstmt.close();
1031        } else {
1032            // didn't find the row
1033            return true;
1034        }
1035
1036        // no conflict
1037        return false;
1038    }
1039
1040    /**
1041     * Sets the reader for this writer to the given reader.
1042     *
1043     * @throws SQLException if a database access error occurs
1044     */
1045    public void setReader(CachedRowSetReader reader) throws SQLException {
1046        this.reader = reader;
1047    }
1048
1049    /**
1050     * Gets the reader for this writer.
1051     *
1052     * @throws SQLException if a database access error occurs
1053     */
1054    public CachedRowSetReader getReader() throws SQLException {
1055        return reader;
1056    }
1057
1058    /**
1059     * Composes a <code>SELECT</code>, <code>UPDATE</code>, <code>INSERT</code>,
1060     * and <code>DELETE</code> statement that can be used by this writer to
1061     * write data to the data source backing the given <code>CachedRowSet</code>
1062     * object.
1063     *
1064     * @ param caller a <code>CachedRowSet</code> object for which this
1065     *                <code>CachedRowSetWriter</code> object is the writer
1066     * @throws SQLException if a database access error occurs
1067     */
1068    private void initSQLStatements(CachedRowSet caller) throws SQLException {
1069
1070        int i;
1071
1072        callerMd = caller.getMetaData();
1073        callerColumnCount = callerMd.getColumnCount();
1074        if (callerColumnCount < 1)
1075            // No data, so return.
1076            return;
1077
1078        /*
1079         * If the RowSet has a Table name we should use it.
1080         * This is really a hack to get round the fact that
1081         * a lot of the jdbc drivers can't provide the tab.
1082         */
1083        String table = caller.getTableName();
1084        if (table == null) {
1085            /*
1086             * attempt to build a table name using the info
1087             * that the driver gave us for the first column
1088             * in the source result set.
1089             */
1090            table = callerMd.getTableName(1);
1091            if (table == null || table.length() == 0) {
1092                throw new SQLException(resBundle.handleGetObject("crswriter.tname").toString());
1093            }
1094        }
1095        String catalog = callerMd.getCatalogName(1);
1096            String schema = callerMd.getSchemaName(1);
1097        DatabaseMetaData dbmd = con.getMetaData();
1098
1099        /*
1100         * Compose a SELECT statement.  There are three parts.
1101         */
1102
1103        // Project List
1104        selectCmd = "SELECT ";
1105        for (i=1; i <= callerColumnCount; i++) {
1106            selectCmd += callerMd.getColumnName(i);
1107            if ( i <  callerMd.getColumnCount() )
1108                selectCmd += ", ";
1109            else
1110                selectCmd += " ";
1111        }
1112
1113        // FROM clause.
1114        selectCmd += "FROM " + buildTableName(dbmd, catalog, schema, table);
1115
1116        /*
1117         * Compose an UPDATE statement.
1118         */
1119        updateCmd = "UPDATE " + buildTableName(dbmd, catalog, schema, table);
1120
1121
1122        /**
1123         *  The following block of code is for checking a particular type of
1124         *  query where in there is a where clause. Without this block, if a
1125         *  SQL statement is built the "where" clause will appear twice hence
1126         *  the DB errors out and a SQLException is thrown. This code also
1127         *  considers that the where clause is in the right place as the
1128         *  CachedRowSet object would already have been populated with this
1129         *  query before coming to this point.
1130         **/
1131
1132        String tempupdCmd = updateCmd.toLowerCase();
1133
1134        int idxupWhere = tempupdCmd.indexOf("where");
1135
1136        if(idxupWhere != -1)
1137        {
1138           updateCmd = updateCmd.substring(0,idxupWhere);
1139        }
1140        updateCmd += "SET ";
1141
1142        /*
1143         * Compose an INSERT statement.
1144         */
1145        insertCmd = "INSERT INTO " + buildTableName(dbmd, catalog, schema, table);
1146        // Column list
1147        insertCmd += "(";
1148        for (i=1; i <= callerColumnCount; i++) {
1149            insertCmd += callerMd.getColumnName(i);
1150            if ( i <  callerMd.getColumnCount() )
1151                insertCmd += ", ";
1152            else
1153                insertCmd += ") VALUES (";
1154        }
1155        for (i=1; i <= callerColumnCount; i++) {
1156            insertCmd += "?";
1157            if (i < callerColumnCount)
1158                insertCmd += ", ";
1159            else
1160                insertCmd += ")";
1161        }
1162
1163        /*
1164         * Compose a DELETE statement.
1165         */
1166        deleteCmd = "DELETE FROM " + buildTableName(dbmd, catalog, schema, table);
1167
1168        /*
1169         * set the key desriptors that will be
1170         * needed to construct where clauses.
1171         */
1172        buildKeyDesc(caller);
1173    }
1174
1175    /**
1176     * Returns a fully qualified table name built from the given catalog and
1177     * table names. The given metadata object is used to get the proper order
1178     * and separator.
1179     *
1180     * @param dbmd a <code>DatabaseMetaData</code> object that contains metadata
1181     *          about this writer's <code>CachedRowSet</code> object
1182     * @param catalog a <code>String</code> object with the rowset's catalog
1183     *          name
1184     * @param table a <code>String</code> object with the name of the table from
1185     *          which this writer's rowset was derived
1186     * @return a <code>String</code> object with the fully qualified name of the
1187     *          table from which this writer's rowset was derived
1188     * @throws SQLException if a database access error occurs
1189     */
1190    private String buildTableName(DatabaseMetaData dbmd,
1191        String catalog, String schema, String table) throws SQLException {
1192
1193       // trim all the leading and trailing whitespaces,
1194       // white spaces can never be catalog, schema or a table name.
1195
1196        String cmd = "";
1197
1198        catalog = catalog.trim();
1199        schema = schema.trim();
1200        table = table.trim();
1201
1202        if (dbmd.isCatalogAtStart() == true) {
1203            if (catalog != null && catalog.length() > 0) {
1204                cmd += catalog + dbmd.getCatalogSeparator();
1205            }
1206            if (schema != null && schema.length() > 0) {
1207                cmd += schema + ".";
1208            }
1209            cmd += table;
1210        } else {
1211            if (schema != null && schema.length() > 0) {
1212                cmd += schema + ".";
1213            }
1214            cmd += table;
1215            if (catalog != null && catalog.length() > 0) {
1216                cmd += dbmd.getCatalogSeparator() + catalog;
1217            }
1218        }
1219        cmd += " ";
1220        return cmd;
1221    }
1222
1223    /**
1224     * Assigns to the given <code>CachedRowSet</code> object's
1225     * <code>params</code>
1226     * field an array whose length equals the number of columns needed
1227     * to uniquely identify a row in the rowset. The array is given
1228     * values by the method <code>buildWhereClause</code>.
1229     * <P>
1230     * If the <code>CachedRowSet</code> object's <code>keyCols</code>
1231     * field has length <code>0</code> or is <code>null</code>, the array
1232     * is set with the column number of every column in the rowset.
1233     * Otherwise, the array in the field <code>keyCols</code> is set with only
1234     * the column numbers of the columns that are required to form a unique
1235     * identifier for a row.
1236     *
1237     * @param crs the <code>CachedRowSet</code> object for which this
1238     *     <code>CachedRowSetWriter</code> object is the writer
1239     *
1240     * @throws SQLException if a database access error occurs
1241     */
1242    private void buildKeyDesc(CachedRowSet crs) throws SQLException {
1243
1244        keyCols = crs.getKeyColumns();
1245        ResultSetMetaData resultsetmd = crs.getMetaData();
1246        if (keyCols == null || keyCols.length == 0) {
1247            ArrayList<Integer> listKeys = new ArrayList<Integer>();
1248
1249            for (int i = 0; i < callerColumnCount; i++ ) {
1250                if(resultsetmd.getColumnType(i+1) != java.sql.Types.CLOB &&
1251                        resultsetmd.getColumnType(i+1) != java.sql.Types.STRUCT &&
1252                        resultsetmd.getColumnType(i+1) != java.sql.Types.SQLXML &&
1253                        resultsetmd.getColumnType(i+1) != java.sql.Types.BLOB &&
1254                        resultsetmd.getColumnType(i+1) != java.sql.Types.ARRAY &&
1255                        resultsetmd.getColumnType(i+1) != java.sql.Types.OTHER )
1256                    listKeys.add(i+1);
1257            }
1258            keyCols = new int[listKeys.size()];
1259            for (int i = 0; i < listKeys.size(); i++ )
1260                keyCols[i] = listKeys.get(i);
1261        }
1262        params = new Object[keyCols.length];
1263    }
1264
1265    /**
1266         * Constructs an SQL <code>WHERE</code> clause using the given
1267         * string as a starting point. The resulting clause will contain
1268         * a column name and " = ?" for each key column, that is, each column
1269         * that is needed to form a unique identifier for a row in the rowset.
1270         * This <code>WHERE</code> clause can be added to
1271         * a <code>PreparedStatement</code> object that updates, inserts, or
1272         * deletes a row.
1273         * <P>
1274         * This method uses the given result set to access values in the
1275         * <code>CachedRowSet</code> object that called this writer.  These
1276         * values are used to build the array of parameters that will serve as
1277         * replacements for the "?" parameter placeholders in the
1278         * <code>PreparedStatement</code> object that is sent to the
1279         * <code>CachedRowSet</code> object's underlying data source.
1280         *
1281         * @param whereClause a <code>String</code> object that is an empty
1282         *                    string ("")
1283         * @param rs a <code>ResultSet</code> object that can be used
1284         *           to access the <code>CachedRowSet</code> object's data
1285         * @return a <code>WHERE</code> clause of the form "<code>WHERE</code>
1286         *         columnName = ? AND columnName = ? AND columnName = ? ..."
1287         * @throws SQLException if a database access error occurs
1288         */
1289    private String buildWhereClause(String whereClause,
1290                                    ResultSet rs) throws SQLException {
1291        whereClause = "WHERE ";
1292
1293        for (int i = 0; i < keyCols.length; i++) {
1294            if (i > 0) {
1295                    whereClause += "AND ";
1296            }
1297            whereClause += callerMd.getColumnName(keyCols[i]);
1298            params[i] = rs.getObject(keyCols[i]);
1299            if (rs.wasNull() == true) {
1300                whereClause += " IS NULL ";
1301            } else {
1302                whereClause += " = ? ";
1303            }
1304        }
1305        return whereClause;
1306    }
1307
1308    void updateResolvedConflictToDB(CachedRowSet crs, Connection con) throws SQLException {
1309          //String updateExe = ;
1310          PreparedStatement pStmt  ;
1311          String strWhere = "WHERE " ;
1312          String strExec =" ";
1313          String strUpdate = "UPDATE ";
1314          int icolCount = crs.getMetaData().getColumnCount();
1315          int keyColumns[] = crs.getKeyColumns();
1316          Object param[];
1317          String strSet="";
1318
1319        strWhere = buildWhereClause(strWhere, crs);
1320
1321        if (keyColumns == null || keyColumns.length == 0) {
1322            keyColumns = new int[icolCount];
1323            for (int i = 0; i < keyColumns.length; ) {
1324                keyColumns[i] = ++i;
1325            }
1326          }
1327          param = new Object[keyColumns.length];
1328
1329         strUpdate = "UPDATE " + buildTableName(con.getMetaData(),
1330                            crs.getMetaData().getCatalogName(1),
1331                           crs.getMetaData().getSchemaName(1),
1332                           crs.getTableName());
1333
1334         // changed or updated values will become part of
1335         // set clause here
1336         strUpdate += "SET ";
1337
1338        boolean first = true;
1339
1340        for (int i=1; i<=icolCount;i++) {
1341           if (crs.columnUpdated(i)) {
1342                  if (first == false) {
1343                    strSet += ", ";
1344                  }
1345                 strSet += crs.getMetaData().getColumnName(i);
1346                 strSet += " = ? ";
1347                 first = false;
1348         } //end if
1349      } //end for
1350
1351         // keycols will become part of where clause
1352         strUpdate += strSet;
1353         strWhere = "WHERE ";
1354
1355        for (int i = 0; i < keyColumns.length; i++) {
1356            if (i > 0) {
1357                    strWhere += "AND ";
1358            }
1359            strWhere += crs.getMetaData().getColumnName(keyColumns[i]);
1360            param[i] = crs.getObject(keyColumns[i]);
1361            if (crs.wasNull() == true) {
1362                strWhere += " IS NULL ";
1363            } else {
1364                strWhere += " = ? ";
1365            }
1366        }
1367          strUpdate += strWhere;
1368
1369        pStmt = con.prepareStatement(strUpdate);
1370
1371        int idx =0;
1372          for (int i = 0; i < icolCount; i++) {
1373             if(crs.columnUpdated(i+1)) {
1374              Object obj = crs.getObject(i+1);
1375              if (obj != null) {
1376                  pStmt.setObject(++idx, obj);
1377              } else {
1378                  pStmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));
1379             } //end if ..else
1380           } //end if crs.column...
1381        } //end for
1382
1383          // Set the key cols for after WHERE =? clause
1384          for (int i = 0; i < keyColumns.length; i++) {
1385              if (param[i] != null) {
1386                  pStmt.setObject(++idx, param[i]);
1387              }
1388          }
1389
1390        int id = pStmt.executeUpdate();
1391      }
1392
1393
1394    /**
1395     *
1396     */
1397    public void commit() throws SQLException {
1398        con.commit();
1399        if (reader.getCloseConnection() == true) {
1400            con.close();
1401        }
1402    }
1403
1404     public void commit(CachedRowSetImpl crs, boolean updateRowset) throws SQLException {
1405        con.commit();
1406        if(updateRowset) {
1407          if(crs.getCommand() != null)
1408            crs.execute(con);
1409        }
1410
1411        if (reader.getCloseConnection() == true) {
1412            con.close();
1413        }
1414    }
1415
1416    /**
1417     *
1418     */
1419    public void rollback() throws SQLException {
1420        con.rollback();
1421        if (reader.getCloseConnection() == true) {
1422            con.close();
1423        }
1424    }
1425
1426    /**
1427     *
1428     */
1429    public void rollback(Savepoint s) throws SQLException {
1430        con.rollback(s);
1431        if (reader.getCloseConnection() == true) {
1432            con.close();
1433        }
1434    }
1435
1436    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
1437        // Default state initialization happens here
1438        ois.defaultReadObject();
1439        // Initialization of  Res Bundle happens here .
1440        try {
1441           resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
1442        } catch(IOException ioe) {
1443            throw new RuntimeException(ioe);
1444        }
1445
1446    }
1447
1448    static final long serialVersionUID =-8506030970299413976L;
1449
1450    /**
1451     * Validate whether the Primary Key is known to the CachedRowSet.  If it is
1452     * not, it is an auto-generated key
1453     * @param pk - Primary Key to validate
1454     * @param rsmd - ResultSetMetadata for the RowSet
1455     * @return true if found, false otherwise (auto generated key)
1456     */
1457    private boolean isPKNameValid(String pk, ResultSetMetaData rsmd) throws SQLException {
1458        boolean isValid = false;
1459        int cols = rsmd.getColumnCount();
1460        for(int i = 1; i<= cols; i++) {
1461            String colName = rsmd.getColumnClassName(i);
1462            if(colName.equalsIgnoreCase(pk)) {
1463                isValid = true;
1464                break;
1465            }
1466        }
1467
1468        return isValid;
1469    }
1470}
1471