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