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         boolean showDel = false;
 269         PreparedStatement pstmtIns = null;
 270         iChangedValsInDbAndCRS = 0;
 271         iChangedValsinDbOnly = 0;
 272 
 273         // We assume caller is a CachedRowSet
 274         CachedRowSetImpl crs = (CachedRowSetImpl)caller;
 275         // crsResolve = new CachedRowSetImpl();
 276         this.crsResolve = new CachedRowSetImpl();;
 277 
 278         // The reader is registered with the writer at design time.
 279         // This is not required, in general.  The reader has logic
 280         // to get a JDBC connection, so call it.
 281 
 282         con = reader.connect(caller);
 283 
 284 
 285         if (con == null) {
 286             throw new SQLException(resBundle.handleGetObject("crswriter.connect").toString());
 287         }
 288 
 289         /*
 290          // Fix 6200646.
 291          // Don't change the connection or transaction properties. This will fail in a
 292          // J2EE container.
 293         if (con.getAutoCommit() == true)  {
 294             con.setAutoCommit(false);
 295         }
 296 
 297         con.setTransactionIsolation(crs.getTransactionIsolation());
 298         */
 299 
 300         initSQLStatements(crs);
 301         int iColCount;
 302 
 303         RowSetMetaDataImpl rsmdWrite = (RowSetMetaDataImpl)crs.getMetaData();
 304         RowSetMetaDataImpl rsmdResolv = new RowSetMetaDataImpl();
 305 
 306         iColCount = rsmdWrite.getColumnCount();
 307         int sz= crs.size()+1;
 308         status = new ArrayList<>(sz);
 309 
 310         status.add(0,null);
 311         rsmdResolv.setColumnCount(iColCount);
 312 
 313         for(int i =1; i <= iColCount; i++) {
 314             rsmdResolv.setColumnType(i, rsmdWrite.getColumnType(i));
 315             rsmdResolv.setColumnName(i, rsmdWrite.getColumnName(i));
 316             rsmdResolv.setNullable(i, ResultSetMetaData.columnNullableUnknown);
 317         }
 318         this.crsResolve.setMetaData(rsmdResolv);
 319 
 320         // moved outside the insert inner loop
 321         //pstmtIns = con.prepareStatement(insertCmd);
 322 
 323         if (callerColumnCount < 1) {
 324             // No data, so return success.
 325             if (reader.getCloseConnection() == true)
 326                     con.close();
 327             return true;
 328         }
 329         // We need to see rows marked for deletion.
 330         showDel = crs.getShowDeleted();
 331         crs.setShowDeleted(true);
 332 
 333         // Look at all the rows.
 334         crs.beforeFirst();
 335 
 336         int rows =1;
 337         while (crs.next()) {
 338             if (crs.rowDeleted()) {
 339                 // The row has been deleted.
 340                 if (conflict = (deleteOriginalRow(crs, this.crsResolve)) == true) {
 341                        status.add(rows, SyncResolver.DELETE_ROW_CONFLICT);
 342                 } else {
 343                       // delete happened without any occurrence of conflicts
 344                       // so update status accordingly
 345                        status.add(rows, SyncResolver.NO_ROW_CONFLICT);
 346                 }
 347 
 348            } else if (crs.rowInserted()) {
 349                 // The row has been inserted.
 350 
 351                 pstmtIns = con.prepareStatement(insertCmd);
 352                 if ( (conflict = insertNewRow(crs, pstmtIns, this.crsResolve)) == true) {
 353                           status.add(rows, SyncResolver.INSERT_ROW_CONFLICT);
 354                 } else {
 355                       // insert happened without any occurrence of conflicts
 356                       // so update status accordingly
 357                        status.add(rows, SyncResolver.NO_ROW_CONFLICT);
 358                 }
 359             } else  if (crs.rowUpdated()) {
 360                   // The row has been updated.
 361                        if ( conflict = (updateOriginalRow(crs)) == true) {
 362                              status.add(rows, SyncResolver.UPDATE_ROW_CONFLICT);
 363                } else {
 364                       // update happened without any occurrence of conflicts
 365                       // so update status accordingly
 366                       status.add(rows, SyncResolver.NO_ROW_CONFLICT);
 367                }
 368 
 369             } else {
 370                /** The row is neither of inserted, updated or deleted.
 371                 *  So set nulls in the this.crsResolve for this row,
 372                 *  as nothing is to be done for such rows.
 373                 *  Also note that if such a row has been changed in database
 374                 *  and we have not changed(inserted, updated or deleted)
 375                 *  that is fine.
 376                 **/
 377                 int icolCount = crs.getMetaData().getColumnCount();
 378                 status.add(rows, SyncResolver.NO_ROW_CONFLICT);
 379 
 380                 this.crsResolve.moveToInsertRow();
 381                 for(int cols=0;cols<iColCount;cols++) {
 382                    this.crsResolve.updateNull(cols+1);
 383                 } //end for
 384 
 385                 this.crsResolve.insertRow();
 386                 this.crsResolve.moveToCurrentRow();
 387 
 388                 } //end if
 389          rows++;
 390       } //end while
 391 
 392         // close the insert statement
 393         if(pstmtIns!=null)
 394         pstmtIns.close();
 395         // reset
 396         crs.setShowDeleted(showDel);
 397 
 398       boolean boolConf = false;
 399       for (int j=1;j<status.size();j++){
 400           // ignore status for index = 0 which is set to null
 401           if(! ((status.get(j)).equals(SyncResolver.NO_ROW_CONFLICT))) {
 402               // there is at least one conflict which needs to be resolved
 403               boolConf = true;
 404              break;
 405           }
 406       }
 407 
 408         crs.beforeFirst();
 409         this.crsResolve.beforeFirst();
 410 
 411     if(boolConf) {
 412         SyncProviderException spe = new SyncProviderException(status.size() - 1+resBundle.handleGetObject("crswriter.conflictsno").toString());
 413         //SyncResolver syncRes = spe.getSyncResolver();
 414 
 415          SyncResolverImpl syncResImpl = (SyncResolverImpl) spe.getSyncResolver();
 416 
 417          syncResImpl.setCachedRowSet(crs);
 418          syncResImpl.setCachedRowSetResolver(this.crsResolve);
 419 
 420          syncResImpl.setStatus(status);
 421          syncResImpl.setCachedRowSetWriter(this);
 422 
 423         throw spe;
 424     } else {
 425          return true;
 426     }
 427        /*
 428        if (conflict == true) {
 429             con.rollback();
 430             return false;
 431         } else {
 432             con.commit();
 433                 if (reader.getCloseConnection() == true) {
 434                        con.close();
 435                 }
 436             return true;
 437         }
 438         */
 439 
 440   } //end writeData
 441 
 442 /**
 443  * Updates the given <code>CachedRowSet</code> object's underlying data
 444  * source so that updates to the rowset are reflected in the original
 445  * data source, and returns <code>false</code> if the update was successful.
 446  * A return value of <code>true</code> indicates that there is a conflict,
 447  * meaning that a value updated in the rowset has already been changed by
 448  * someone else in the underlying data source.  A conflict can also exist
 449  * if, for example, more than one row in the data source would be affected
 450  * by the update or if no rows would be affected.  In any case, if there is
 451  * a conflict, this method does not update the underlying data source.
 452  * <P>
 453  * This method is called internally by the method <code>writeData</code>
 454  * if a row in the <code>CachedRowSet</code> object for which this
 455  * <code>CachedRowSetWriter</code> object is the writer has been updated.
 456  *
 457  * @return <code>false</code> if the update to the underlying data source is
 458  *         successful; <code>true</code> otherwise
 459  * @throws SQLException if a database access error occurs
 460  */
 461     private boolean updateOriginalRow(CachedRowSet crs)
 462         throws SQLException {
 463         PreparedStatement pstmt;
 464         int i = 0;
 465         int idx = 0;
 466 
 467         // Select the row from the database.
 468         ResultSet origVals = crs.getOriginalRow();
 469         origVals.next();
 470 
 471         try {
 472             updateWhere = buildWhereClause(updateWhere, origVals);
 473 
 474 
 475              /**
 476               *  The following block of code is for checking a particular type of
 477               *  query where in there is a where clause. Without this block, if a
 478               *  SQL statement is built the "where" clause will appear twice hence
 479               *  the DB errors out and a SQLException is thrown. This code also
 480               *  considers that the where clause is in the right place as the
 481               *  CachedRowSet object would already have been populated with this
 482               *  query before coming to this point.
 483               **/
 484 
 485 
 486             String tempselectCmd = selectCmd.toLowerCase();
 487 
 488             int idxWhere = tempselectCmd.indexOf("where");
 489 
 490             if(idxWhere != -1)
 491             {
 492                String tempSelect = selectCmd.substring(0,idxWhere);
 493                selectCmd = tempSelect;
 494             }
 495 
 496             pstmt = con.prepareStatement(selectCmd + updateWhere,
 497                         ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
 498 
 499             for (i = 0; i < keyCols.length; i++) {
 500                 if (params[i] != null) {
 501                     pstmt.setObject(++idx, params[i]);
 502                 } else {
 503                     continue;
 504                 }
 505             }
 506 
 507             try {
 508                 pstmt.setMaxRows(crs.getMaxRows());
 509                 pstmt.setMaxFieldSize(crs.getMaxFieldSize());
 510                 pstmt.setEscapeProcessing(crs.getEscapeProcessing());
 511                 pstmt.setQueryTimeout(crs.getQueryTimeout());
 512             } catch (Exception ex) {
 513                 // Older driver don't support these operations.
 514             }
 515 
 516             ResultSet rs = null;
 517             rs = pstmt.executeQuery();
 518             ResultSetMetaData rsmd = rs.getMetaData();
 519 
 520             if (rs.next()) {
 521                 if (rs.next()) {
 522                    /** More than one row conflict.
 523                     *  If rs has only one row we are able to
 524                     *  uniquely identify the row where update
 525                     *  have to happen else if more than one
 526                     *  row implies we cannot uniquely identify the row
 527                     *  where we have to do updates.
 528                     *  crs.setKeyColumns needs to be set to
 529                     *  come out of this situation.
 530                     */
 531 
 532                    return true;
 533                 }
 534 
 535                 // don't close the rs
 536                 // we require the record in rs to be used.
 537                 // rs.close();
 538                 // pstmt.close();
 539                 rs.first();
 540 
 541                 // how many fields need to be updated
 542                 int colsNotChanged = 0;
 543                 Vector<Integer> cols = new Vector<>();
 544                 String updateExec = updateCmd;
 545                 Object orig;
 546                 Object curr;
 547                 Object rsval;
 548                 boolean boolNull = true;
 549                 Object objVal = null;
 550 
 551                 // There's only one row and the cursor
 552                 // needs to be on that row.
 553 
 554                 boolean first = true;
 555                 boolean flag = true;
 556 
 557           this.crsResolve.moveToInsertRow();
 558 
 559           for (i = 1; i <= callerColumnCount; i++) {
 560                 orig = origVals.getObject(i);
 561                 curr = crs.getObject(i);
 562                 rsval = rs.getObject(i);
 563                 /*
 564                  * the following block creates equivalent objects
 565                  * that would have been created if this rs is populated
 566                  * into a CachedRowSet so that comparison of the column values
 567                  * from the ResultSet and CachedRowSet are possible
 568                  */
 569                 Map<String, Class<?>> map = (crs.getTypeMap() == null)?con.getTypeMap():crs.getTypeMap();
 570                 if (rsval instanceof Struct) {
 571 
 572                     Struct s = (Struct)rsval;
 573 
 574                     // look up the class in the map
 575                     Class<?> c = null;
 576                     c = map.get(s.getSQLTypeName());
 577                     if (c != null) {
 578                         // create new instance of the class
 579                         SQLData obj = null;
 580                         try {
 581                             obj = (SQLData)c.newInstance();
 582                         } catch (java.lang.InstantiationException ex) {
 583                             throw new SQLException(MessageFormat.format(resBundle.handleGetObject("cachedrowsetimpl.unableins").toString(),
 584                             ex.getMessage()));
 585                         } catch (java.lang.IllegalAccessException ex) {
 586                             throw new SQLException(MessageFormat.format(resBundle.handleGetObject("cachedrowsetimpl.unableins").toString(),
 587                             ex.getMessage()));
 588                         }
 589                         // get the attributes from the struct
 590                         Object attribs[] = s.getAttributes(map);
 591                         // create the SQLInput "stream"
 592                         SQLInputImpl sqlInput = new SQLInputImpl(attribs, map);
 593                         // read the values...
 594                         obj.readSQL(sqlInput, s.getSQLTypeName());
 595                         rsval = obj;
 596                     }
 597                 } else if (rsval instanceof SQLData) {
 598                     rsval = new SerialStruct((SQLData)rsval, map);
 599                 } else if (rsval instanceof Blob) {
 600                     rsval = new SerialBlob((Blob)rsval);
 601                 } else if (rsval instanceof Clob) {
 602                     rsval = new SerialClob((Clob)rsval);
 603                 } else if (rsval instanceof java.sql.Array) {
 604                     rsval = new SerialArray((java.sql.Array)rsval, map);
 605                 }
 606 
 607                 // reset boolNull if it had been set
 608                 boolNull = true;
 609 
 610                 /** This addtional checking has been added when the current value
 611                  *  in the DB is null, but the DB had a different value when the
 612                  *  data was actaully fetched into the CachedRowSet.
 613                  **/
 614 
 615                 if(rsval == null && orig != null) {
 616                    // value in db has changed
 617                     // don't proceed with synchronization
 618                     // get the value in db and pass it to the resolver.
 619 
 620                     iChangedValsinDbOnly++;
 621                    // Set the boolNull to false,
 622                    // in order to set the actual value;
 623                      boolNull = false;
 624                      objVal = rsval;
 625                 }
 626 
 627                 /** Adding the checking for rsval to be "not" null or else
 628                  *  it would through a NullPointerException when the values
 629                  *  are compared.
 630                  **/
 631 
 632                 else if(rsval != null && (!rsval.equals(orig)))
 633                 {
 634                     // value in db has changed
 635                     // don't proceed with synchronization
 636                     // get the value in db and pass it to the resolver.
 637 
 638                     iChangedValsinDbOnly++;
 639                    // Set the boolNull to false,
 640                    // in order to set the actual value;
 641                      boolNull = false;
 642                      objVal = rsval;
 643                 } else if (  (orig == null || curr == null) ) {
 644 
 645                         /** Adding the additonal condition of checking for "flag"
 646                          *  boolean variable, which would otherwise result in
 647                          *  building a invalid query, as the comma would not be
 648                          *  added to the query string.
 649                          **/
 650 
 651                         if (first == false || flag == false) {
 652                           updateExec += ", ";
 653                          }
 654                         updateExec += crs.getMetaData().getColumnName(i);
 655                         cols.add(i);
 656                         updateExec += " = ? ";
 657                         first = false;
 658 
 659                 /** Adding the extra condition for orig to be "not" null as the
 660                  *  condition for orig to be null is take prior to this, if this
 661                  *  is not added it will result in a NullPointerException when
 662                  *  the values are compared.
 663                  **/
 664 
 665                 }  else if (orig.equals(curr)) {
 666                        colsNotChanged++;
 667                      //nothing to update in this case since values are equal
 668 
 669                 /** Adding the extra condition for orig to be "not" null as the
 670                  *  condition for orig to be null is take prior to this, if this
 671                  *  is not added it will result in a NullPointerException when
 672                  *  the values are compared.
 673                  **/
 674 
 675                 } else if(orig.equals(curr) == false) {
 676                       // When values from db and values in CachedRowSet are not equal,
 677                       // if db value is same as before updation for each col in
 678                       // the row before fetching into CachedRowSet,
 679                       // only then we go ahead with updation, else we
 680                       // throw SyncProviderException.
 681 
 682                       // if value has changed in db after fetching from db
 683                       // for some cols of the row and at the same time, some other cols
 684                       // have changed in CachedRowSet, no synchronization happens
 685 
 686                       // Synchronization happens only when data when fetching is
 687                       // same or at most has changed in cachedrowset
 688 
 689                       // check orig value with what is there in crs for a column
 690                       // before updation in crs.
 691 
 692                          if(crs.columnUpdated(i)) {
 693                              if(rsval.equals(orig)) {
 694                                // At this point we are sure that
 695                                // the value updated in crs was from
 696                                // what is in db now and has not changed
 697                                  if (flag == false || first == false) {
 698                                     updateExec += ", ";
 699                                  }
 700                                 updateExec += crs.getMetaData().getColumnName(i);
 701                                 cols.add(i);
 702                                 updateExec += " = ? ";
 703                                 flag = false;
 704                              } else {
 705                                // Here the value has changed in the db after
 706                                // data was fetched
 707                                // Plus store this row from CachedRowSet and keep it
 708                                // in a new CachedRowSet
 709                                boolNull= false;
 710                                objVal = rsval;
 711                                iChangedValsInDbAndCRS++;
 712                              }
 713                          }
 714                   }
 715 
 716                     if(!boolNull) {
 717                         this.crsResolve.updateObject(i,objVal);
 718                                  } else {
 719                                       this.crsResolve.updateNull(i);
 720                                  }
 721                 } //end for
 722 
 723                 rs.close();
 724                 pstmt.close();
 725 
 726                this.crsResolve.insertRow();
 727                    this.crsResolve.moveToCurrentRow();
 728 
 729                 /**
 730                  * if nothing has changed return now - this can happen
 731                  * if column is updated to the same value.
 732                  * if colsNotChanged == callerColumnCount implies we are updating
 733                  * the database with ALL COLUMNS HAVING SAME VALUES,
 734                  * so skip going to database, else do as usual.
 735                  **/
 736                 if ( (first == false && cols.size() == 0)  ||
 737                      colsNotChanged == callerColumnCount ) {
 738                     return false;
 739                 }
 740 
 741                 if(iChangedValsInDbAndCRS != 0 || iChangedValsinDbOnly != 0) {
 742                    return true;
 743                 }
 744 
 745 
 746                 updateExec += updateWhere;
 747 
 748                 pstmt = con.prepareStatement(updateExec);
 749 
 750                 // Comments needed here
 751                 for (i = 0; i < cols.size(); i++) {
 752                     Object obj = crs.getObject(cols.get(i));
 753                     if (obj != null)
 754                         pstmt.setObject(i + 1, obj);
 755                     else
 756                         pstmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));
 757                 }
 758                 idx = i;
 759 
 760                 // Comments needed here
 761                 for (i = 0; i < keyCols.length; i++) {
 762                     if (params[i] != null) {
 763                         pstmt.setObject(++idx, params[i]);
 764                     } else {
 765                         continue;
 766                     }
 767                 }
 768 
 769                 i = pstmt.executeUpdate();
 770 
 771                /**
 772                 * i should be equal to 1(row count), because we update
 773                 * one row(returned as row count) at a time, if all goes well.
 774                 * if 1 != 1, this implies we have not been able to
 775                 * do updations properly i.e there is a conflict in database
 776                 * versus what is in CachedRowSet for this particular row.
 777                 **/
 778 
 779                  return false;
 780 
 781             } else {
 782                 /**
 783                  * Cursor will be here, if the ResultSet may not return even a single row
 784                  * i.e. we can't find the row where to update because it has been deleted
 785                  * etc. from the db.
 786                  * Present the whole row as null to user, to force null to be sync'ed
 787                  * and hence nothing to be synced.
 788                  *
 789                  * NOTE:
 790                  * ------
 791                  * In the database if a column that is mapped to java.sql.Types.REAL stores
 792                  * a Double value and is compared with value got from ResultSet.getFloat()
 793                  * no row is retrieved and will throw a SyncProviderException. For details
 794                  * see bug Id 5053830
 795                  **/
 796                 return true;
 797             }
 798         } catch (SQLException ex) {
 799             ex.printStackTrace();
 800             // if executeUpdate fails it will come here,
 801             // update crsResolve with null rows
 802             this.crsResolve.moveToInsertRow();
 803 
 804             for(i = 1; i <= callerColumnCount; i++) {
 805                this.crsResolve.updateNull(i);
 806             }
 807 
 808             this.crsResolve.insertRow();
 809             this.crsResolve.moveToCurrentRow();
 810 
 811             return true;
 812         }
 813     }
 814 
 815    /**
 816     * Inserts a row that has been inserted into the given
 817     * <code>CachedRowSet</code> object into the data source from which
 818     * the rowset is derived, returning <code>false</code> if the insertion
 819     * was successful.
 820     *
 821     * @param crs the <code>CachedRowSet</code> object that has had a row inserted
 822     *            and to whose underlying data source the row will be inserted
 823     * @param pstmt the <code>PreparedStatement</code> object that will be used
 824     *              to execute the insertion
 825     * @return <code>false</code> to indicate that the insertion was successful;
 826     *         <code>true</code> otherwise
 827     * @throws SQLException if a database access error occurs
 828     */
 829    private boolean insertNewRow(CachedRowSet crs,
 830        PreparedStatement pstmt, CachedRowSetImpl crsRes) throws SQLException {
 831 
 832        boolean returnVal = false;
 833 
 834        try (PreparedStatement pstmtSel = con.prepareStatement(selectCmd,
 835                        ResultSet.TYPE_SCROLL_SENSITIVE,
 836                        ResultSet.CONCUR_READ_ONLY);
 837             ResultSet rs = pstmtSel.executeQuery();
 838             ResultSet rs2 = con.getMetaData().getPrimaryKeys(null, null,
 839                        crs.getTableName())
 840        ) {
 841 
 842            ResultSetMetaData rsmd = crs.getMetaData();
 843            int icolCount = rsmd.getColumnCount();
 844            String[] primaryKeys = new String[icolCount];
 845            int k = 0;
 846            while (rs2.next()) {
 847                primaryKeys[k] = rs2.getString("COLUMN_NAME");
 848                k++;
 849            }
 850 
 851            if (rs.next()) {
 852                for (String pkName : primaryKeys) {
 853                    if (!isPKNameValid(pkName, rsmd)) {
 854 
 855                        /* We came here as one of the the primary keys
 856                         * of the table is not present in the cached
 857                         * rowset object, it should be an autoincrement column
 858                         * and not included while creating CachedRowSet
 859                         * Object, proceed to check for other primary keys
 860                         */
 861                        continue;
 862                    }
 863 
 864                    Object crsPK = crs.getObject(pkName);
 865                    if (crsPK == null) {
 866                        /*
 867                         * It is possible that the PK is null on some databases
 868                         * and will be filled in at insert time (MySQL for example)
 869                         */
 870                        break;
 871                    }
 872 
 873                    String rsPK = rs.getObject(pkName).toString();
 874                    if (crsPK.toString().equals(rsPK)) {
 875                        returnVal = true;
 876                        this.crsResolve.moveToInsertRow();
 877                        for (int i = 1; i <= icolCount; i++) {
 878                            String colname = (rs.getMetaData()).getColumnName(i);
 879                            if (colname.equals(pkName))
 880                                this.crsResolve.updateObject(i,rsPK);
 881                            else
 882                                this.crsResolve.updateNull(i);
 883                        }
 884                        this.crsResolve.insertRow();
 885                        this.crsResolve.moveToCurrentRow();
 886                    }
 887                }
 888            }
 889 
 890            if (returnVal) {
 891                return returnVal;
 892            }
 893 
 894            try {
 895                for (int i = 1; i <= icolCount; i++) {
 896                    Object obj = crs.getObject(i);
 897                    if (obj != null) {
 898                        pstmt.setObject(i, obj);
 899                    } else {
 900                        pstmt.setNull(i,crs.getMetaData().getColumnType(i));
 901                    }
 902                }
 903 
 904                pstmt.executeUpdate();
 905                return false;
 906 
 907            } catch (SQLException ex) {
 908                /*
 909                 * Cursor will come here if executeUpdate fails.
 910                 * There can be many reasons why the insertion failed,
 911                 * one can be violation of primary key.
 912                 * Hence we cannot exactly identify why the insertion failed
 913                 * Present the current row as a null row to the user.
 914                 */
 915                this.crsResolve.moveToInsertRow();
 916 
 917                for (int i = 1; i <= icolCount; i++) {
 918                    this.crsResolve.updateNull(i);
 919                }
 920 
 921                this.crsResolve.insertRow();
 922                this.crsResolve.moveToCurrentRow();
 923 
 924                return true;
 925            }
 926        }
 927    }
 928 
 929 /**
 930  * Deletes the row in the underlying data source that corresponds to
 931  * a row that has been deleted in the given <code> CachedRowSet</code> object
 932  * and returns <code>false</code> if the deletion was successful.
 933  * <P>
 934  * This method is called internally by this writer's <code>writeData</code>
 935  * method when a row in the rowset has been deleted. The values in the
 936  * deleted row are the same as those that are stored in the original row
 937  * of the given <code>CachedRowSet</code> object.  If the values in the
 938  * original row differ from the row in the underlying data source, the row
 939  * in the data source is not deleted, and <code>deleteOriginalRow</code>
 940  * returns <code>true</code> to indicate that there was a conflict.
 941  *
 942  *
 943  * @return <code>false</code> if the deletion was successful, which means that
 944  *         there was no conflict; <code>true</code> otherwise
 945  * @throws SQLException if there was a database access error
 946  */
 947     private boolean deleteOriginalRow(CachedRowSet crs, CachedRowSetImpl crsRes) throws SQLException {
 948         PreparedStatement pstmt;
 949         int i;
 950         int idx = 0;
 951         String strSelect;
 952     // Select the row from the database.
 953         ResultSet origVals = crs.getOriginalRow();
 954         origVals.next();
 955 
 956         deleteWhere = buildWhereClause(deleteWhere, origVals);
 957         pstmt = con.prepareStatement(selectCmd + deleteWhere,
 958                 ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
 959 
 960         for (i = 0; i < keyCols.length; i++) {
 961             if (params[i] != null) {
 962                 pstmt.setObject(++idx, params[i]);
 963             } else {
 964                 continue;
 965             }
 966         }
 967 
 968         try {
 969             pstmt.setMaxRows(crs.getMaxRows());
 970             pstmt.setMaxFieldSize(crs.getMaxFieldSize());
 971             pstmt.setEscapeProcessing(crs.getEscapeProcessing());
 972             pstmt.setQueryTimeout(crs.getQueryTimeout());
 973         } catch (Exception ex) {
 974             /*
 975              * Older driver don't support these operations...
 976              */
 977             ;
 978         }
 979 
 980         ResultSet rs = pstmt.executeQuery();
 981 
 982         if (rs.next() == true) {
 983             if (rs.next()) {
 984                 // more than one row
 985                 return true;
 986             }
 987             rs.first();
 988 
 989             // Now check all the values in rs to be same in
 990             // db also before actually going ahead with deleting
 991             boolean boolChanged = false;
 992 
 993             crsRes.moveToInsertRow();
 994 
 995             for (i = 1; i <= crs.getMetaData().getColumnCount(); i++) {
 996 
 997                 Object original = origVals.getObject(i);
 998                 Object changed = rs.getObject(i);
 999 
1000                 if(original != null && changed != null ) {
1001                   if(! (original.toString()).equals(changed.toString()) ) {
1002                       boolChanged = true;
1003                       crsRes.updateObject(i,origVals.getObject(i));
1004                   }
1005                 } else {
1006                    crsRes.updateNull(i);
1007                }
1008             }
1009 
1010            crsRes.insertRow();
1011            crsRes.moveToCurrentRow();
1012 
1013            if(boolChanged) {
1014                // do not delete as values in db have changed
1015                // deletion will not happen for this row from db
1016                    // exit now returning true. i.e. conflict
1017                return true;
1018             } else {
1019                 // delete the row.
1020                 // Go ahead with deleting,
1021                 // don't do anything here
1022             }
1023 
1024             String cmd = deleteCmd + deleteWhere;
1025             pstmt = con.prepareStatement(cmd);
1026 
1027             idx = 0;
1028             for (i = 0; i < keyCols.length; i++) {
1029                 if (params[i] != null) {
1030                     pstmt.setObject(++idx, params[i]);
1031                 } else {
1032                     continue;
1033                 }
1034             }
1035 
1036             if (pstmt.executeUpdate() != 1) {
1037                 return true;
1038             }
1039             pstmt.close();
1040         } else {
1041             // didn't find the row
1042             return true;
1043         }
1044 
1045         // no conflict
1046         return false;
1047     }
1048 
1049     /**
1050      * Sets the reader for this writer to the given reader.
1051      *
1052      * @throws SQLException if a database access error occurs
1053      */
1054     public void setReader(CachedRowSetReader reader) throws SQLException {
1055         this.reader = reader;
1056     }
1057 
1058     /**
1059      * Gets the reader for this writer.
1060      *
1061      * @throws SQLException if a database access error occurs
1062      */
1063     public CachedRowSetReader getReader() throws SQLException {
1064         return reader;
1065     }
1066 
1067     /**
1068      * Composes a <code>SELECT</code>, <code>UPDATE</code>, <code>INSERT</code>,
1069      * and <code>DELETE</code> statement that can be used by this writer to
1070      * write data to the data source backing the given <code>CachedRowSet</code>
1071      * object.
1072      *
1073      * @ param caller a <code>CachedRowSet</code> object for which this
1074      *                <code>CachedRowSetWriter</code> object is the writer
1075      * @throws SQLException if a database access error occurs
1076      */
1077     private void initSQLStatements(CachedRowSet caller) throws SQLException {
1078 
1079         int i;
1080 
1081         callerMd = caller.getMetaData();
1082         callerColumnCount = callerMd.getColumnCount();
1083         if (callerColumnCount < 1)
1084             // No data, so return.
1085             return;
1086 
1087         /*
1088          * If the RowSet has a Table name we should use it.
1089          * This is really a hack to get round the fact that
1090          * a lot of the jdbc drivers can't provide the tab.
1091          */
1092         String table = caller.getTableName();
1093         if (table == null) {
1094             /*
1095              * attempt to build a table name using the info
1096              * that the driver gave us for the first column
1097              * in the source result set.
1098              */
1099             table = callerMd.getTableName(1);
1100             if (table == null || table.length() == 0) {
1101                 throw new SQLException(resBundle.handleGetObject("crswriter.tname").toString());
1102             }
1103         }
1104         String catalog = callerMd.getCatalogName(1);
1105             String schema = callerMd.getSchemaName(1);
1106         DatabaseMetaData dbmd = con.getMetaData();
1107 
1108         /*
1109          * Compose a SELECT statement.  There are three parts.
1110          */
1111 
1112         // Project List
1113         selectCmd = "SELECT ";
1114         for (i=1; i <= callerColumnCount; i++) {
1115             selectCmd += callerMd.getColumnName(i);
1116             if ( i <  callerMd.getColumnCount() )
1117                 selectCmd += ", ";
1118             else
1119                 selectCmd += " ";
1120         }
1121 
1122         // FROM clause.
1123         selectCmd += "FROM " + buildTableName(dbmd, catalog, schema, table);
1124 
1125         /*
1126          * Compose an UPDATE statement.
1127          */
1128         updateCmd = "UPDATE " + buildTableName(dbmd, catalog, schema, table);
1129 
1130 
1131         /**
1132          *  The following block of code is for checking a particular type of
1133          *  query where in there is a where clause. Without this block, if a
1134          *  SQL statement is built the "where" clause will appear twice hence
1135          *  the DB errors out and a SQLException is thrown. This code also
1136          *  considers that the where clause is in the right place as the
1137          *  CachedRowSet object would already have been populated with this
1138          *  query before coming to this point.
1139          **/
1140 
1141         String tempupdCmd = updateCmd.toLowerCase();
1142 
1143         int idxupWhere = tempupdCmd.indexOf("where");
1144 
1145         if(idxupWhere != -1)
1146         {
1147            updateCmd = updateCmd.substring(0,idxupWhere);
1148         }
1149         updateCmd += "SET ";
1150 
1151         /*
1152          * Compose an INSERT statement.
1153          */
1154         insertCmd = "INSERT INTO " + buildTableName(dbmd, catalog, schema, table);
1155         // Column list
1156         insertCmd += "(";
1157         for (i=1; i <= callerColumnCount; i++) {
1158             insertCmd += callerMd.getColumnName(i);
1159             if ( i <  callerMd.getColumnCount() )
1160                 insertCmd += ", ";
1161             else
1162                 insertCmd += ") VALUES (";
1163         }
1164         for (i=1; i <= callerColumnCount; i++) {
1165             insertCmd += "?";
1166             if (i < callerColumnCount)
1167                 insertCmd += ", ";
1168             else
1169                 insertCmd += ")";
1170         }
1171 
1172         /*
1173          * Compose a DELETE statement.
1174          */
1175         deleteCmd = "DELETE FROM " + buildTableName(dbmd, catalog, schema, table);
1176 
1177         /*
1178          * set the key desriptors that will be
1179          * needed to construct where clauses.
1180          */
1181         buildKeyDesc(caller);
1182     }
1183 
1184     /**
1185      * Returns a fully qualified table name built from the given catalog and
1186      * table names. The given metadata object is used to get the proper order
1187      * and separator.
1188      *
1189      * @param dbmd a <code>DatabaseMetaData</code> object that contains metadata
1190      *          about this writer's <code>CachedRowSet</code> object
1191      * @param catalog a <code>String</code> object with the rowset's catalog
1192      *          name
1193      * @param table a <code>String</code> object with the name of the table from
1194      *          which this writer's rowset was derived
1195      * @return a <code>String</code> object with the fully qualified name of the
1196      *          table from which this writer's rowset was derived
1197      * @throws SQLException if a database access error occurs
1198      */
1199     private String buildTableName(DatabaseMetaData dbmd,
1200         String catalog, String schema, String table) throws SQLException {
1201 
1202        // trim all the leading and trailing whitespaces,
1203        // white spaces can never be catalog, schema or a table name.
1204 
1205         String cmd = "";
1206 
1207         catalog = catalog.trim();
1208         schema = schema.trim();
1209         table = table.trim();
1210 
1211         if (dbmd.isCatalogAtStart() == true) {
1212             if (catalog != null && catalog.length() > 0) {
1213                 cmd += catalog + dbmd.getCatalogSeparator();
1214             }
1215             if (schema != null && schema.length() > 0) {
1216                 cmd += schema + ".";
1217             }
1218             cmd += table;
1219         } else {
1220             if (schema != null && schema.length() > 0) {
1221                 cmd += schema + ".";
1222             }
1223             cmd += table;
1224             if (catalog != null && catalog.length() > 0) {
1225                 cmd += dbmd.getCatalogSeparator() + catalog;
1226             }
1227         }
1228         cmd += " ";
1229         return cmd;
1230     }
1231 
1232     /**
1233      * Assigns to the given <code>CachedRowSet</code> object's
1234      * <code>params</code>
1235      * field an array whose length equals the number of columns needed
1236      * to uniquely identify a row in the rowset. The array is given
1237      * values by the method <code>buildWhereClause</code>.
1238      * <P>
1239      * If the <code>CachedRowSet</code> object's <code>keyCols</code>
1240      * field has length <code>0</code> or is <code>null</code>, the array
1241      * is set with the column number of every column in the rowset.
1242      * Otherwise, the array in the field <code>keyCols</code> is set with only
1243      * the column numbers of the columns that are required to form a unique
1244      * identifier for a row.
1245      *
1246      * @param crs the <code>CachedRowSet</code> object for which this
1247      *     <code>CachedRowSetWriter</code> object is the writer
1248      *
1249      * @throws SQLException if a database access error occurs
1250      */
1251     private void buildKeyDesc(CachedRowSet crs) throws SQLException {
1252 
1253         keyCols = crs.getKeyColumns();
1254         ResultSetMetaData resultsetmd = crs.getMetaData();
1255         if (keyCols == null || keyCols.length == 0) {
1256             ArrayList<Integer> listKeys = new ArrayList<Integer>();
1257 
1258             for (int i = 0; i < callerColumnCount; i++ ) {
1259                 if(resultsetmd.getColumnType(i+1) != java.sql.Types.CLOB &&
1260                         resultsetmd.getColumnType(i+1) != java.sql.Types.STRUCT &&
1261                         resultsetmd.getColumnType(i+1) != java.sql.Types.SQLXML &&
1262                         resultsetmd.getColumnType(i+1) != java.sql.Types.BLOB &&
1263                         resultsetmd.getColumnType(i+1) != java.sql.Types.ARRAY &&
1264                         resultsetmd.getColumnType(i+1) != java.sql.Types.OTHER )
1265                     listKeys.add(i+1);
1266             }
1267             keyCols = new int[listKeys.size()];
1268             for (int i = 0; i < listKeys.size(); i++ )
1269                 keyCols[i] = listKeys.get(i);
1270         }
1271         params = new Object[keyCols.length];
1272     }
1273 
1274     /**
1275          * Constructs an SQL <code>WHERE</code> clause using the given
1276          * string as a starting point. The resulting clause will contain
1277          * a column name and " = ?" for each key column, that is, each column
1278          * that is needed to form a unique identifier for a row in the rowset.
1279          * This <code>WHERE</code> clause can be added to
1280          * a <code>PreparedStatement</code> object that updates, inserts, or
1281          * deletes a row.
1282          * <P>
1283          * This method uses the given result set to access values in the
1284          * <code>CachedRowSet</code> object that called this writer.  These
1285          * values are used to build the array of parameters that will serve as
1286          * replacements for the "?" parameter placeholders in the
1287          * <code>PreparedStatement</code> object that is sent to the
1288          * <code>CachedRowSet</code> object's underlying data source.
1289          *
1290          * @param whereClause a <code>String</code> object that is an empty
1291          *                    string ("")
1292          * @param rs a <code>ResultSet</code> object that can be used
1293          *           to access the <code>CachedRowSet</code> object's data
1294          * @return a <code>WHERE</code> clause of the form "<code>WHERE</code>
1295          *         columnName = ? AND columnName = ? AND columnName = ? ..."
1296          * @throws SQLException if a database access error occurs
1297          */
1298     private String buildWhereClause(String whereClause,
1299                                     ResultSet rs) throws SQLException {
1300         whereClause = "WHERE ";
1301 
1302         for (int i = 0; i < keyCols.length; i++) {
1303             if (i > 0) {
1304                     whereClause += "AND ";
1305             }
1306             whereClause += callerMd.getColumnName(keyCols[i]);
1307             params[i] = rs.getObject(keyCols[i]);
1308             if (rs.wasNull() == true) {
1309                 whereClause += " IS NULL ";
1310             } else {
1311                 whereClause += " = ? ";
1312             }
1313         }
1314         return whereClause;
1315     }
1316 
1317     void updateResolvedConflictToDB(CachedRowSet crs, Connection con) throws SQLException {
1318           //String updateExe = ;
1319           PreparedStatement pStmt  ;
1320           String strWhere = "WHERE " ;
1321           String strExec =" ";
1322           String strUpdate = "UPDATE ";
1323           int icolCount = crs.getMetaData().getColumnCount();
1324           int keyColumns[] = crs.getKeyColumns();
1325           Object param[];
1326           String strSet="";
1327 
1328         strWhere = buildWhereClause(strWhere, crs);
1329 
1330         if (keyColumns == null || keyColumns.length == 0) {
1331             keyColumns = new int[icolCount];
1332             for (int i = 0; i < keyColumns.length; ) {
1333                 keyColumns[i] = ++i;
1334             }
1335           }
1336           param = new Object[keyColumns.length];
1337 
1338          strUpdate = "UPDATE " + buildTableName(con.getMetaData(),
1339                             crs.getMetaData().getCatalogName(1),
1340                            crs.getMetaData().getSchemaName(1),
1341                            crs.getTableName());
1342 
1343          // changed or updated values will become part of
1344          // set clause here
1345          strUpdate += "SET ";
1346 
1347         boolean first = true;
1348 
1349         for (int i=1; i<=icolCount;i++) {
1350            if (crs.columnUpdated(i)) {
1351                   if (first == false) {
1352                     strSet += ", ";
1353                   }
1354                  strSet += crs.getMetaData().getColumnName(i);
1355                  strSet += " = ? ";
1356                  first = false;
1357          } //end if
1358       } //end for
1359 
1360          // keycols will become part of where clause
1361          strUpdate += strSet;
1362          strWhere = "WHERE ";
1363 
1364         for (int i = 0; i < keyColumns.length; i++) {
1365             if (i > 0) {
1366                     strWhere += "AND ";
1367             }
1368             strWhere += crs.getMetaData().getColumnName(keyColumns[i]);
1369             param[i] = crs.getObject(keyColumns[i]);
1370             if (crs.wasNull() == true) {
1371                 strWhere += " IS NULL ";
1372             } else {
1373                 strWhere += " = ? ";
1374             }
1375         }
1376           strUpdate += strWhere;
1377 
1378         pStmt = con.prepareStatement(strUpdate);
1379 
1380         int idx =0;
1381           for (int i = 0; i < icolCount; i++) {
1382              if(crs.columnUpdated(i+1)) {
1383               Object obj = crs.getObject(i+1);
1384               if (obj != null) {
1385                   pStmt.setObject(++idx, obj);
1386               } else {
1387                   pStmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));
1388              } //end if ..else
1389            } //end if crs.column...
1390         } //end for
1391 
1392           // Set the key cols for after WHERE =? clause
1393           for (int i = 0; i < keyColumns.length; i++) {
1394               if (param[i] != null) {
1395                   pStmt.setObject(++idx, param[i]);
1396               }
1397           }
1398 
1399         int id = pStmt.executeUpdate();
1400       }
1401 
1402 
1403     /**
1404      *
1405      */
1406     public void commit() throws SQLException {
1407         con.commit();
1408         if (reader.getCloseConnection() == true) {
1409             con.close();
1410         }
1411     }
1412 
1413      public void commit(CachedRowSetImpl crs, boolean updateRowset) throws SQLException {
1414         con.commit();
1415         if(updateRowset) {
1416           if(crs.getCommand() != null)
1417             crs.execute(con);
1418         }
1419 
1420         if (reader.getCloseConnection() == true) {
1421             con.close();
1422         }
1423     }
1424 
1425     /**
1426      *
1427      */
1428     public void rollback() throws SQLException {
1429         con.rollback();
1430         if (reader.getCloseConnection() == true) {
1431             con.close();
1432         }
1433     }
1434 
1435     /**
1436      *
1437      */
1438     public void rollback(Savepoint s) throws SQLException {
1439         con.rollback(s);
1440         if (reader.getCloseConnection() == true) {
1441             con.close();
1442         }
1443     }
1444 
1445     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
1446         // Default state initialization happens here
1447         ois.defaultReadObject();
1448         // Initialization of  Res Bundle happens here .
1449         try {
1450            resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
1451         } catch(IOException ioe) {
1452             throw new RuntimeException(ioe);
1453         }
1454 
1455     }
1456 
1457     static final long serialVersionUID =-8506030970299413976L;
1458 
1459     /**
1460      * Validate whether the Primary Key is known to the CachedRowSet.  If it is
1461      * not, it is an auto-generated key
1462      * @param pk - Primary Key to validate
1463      * @param rsmd - ResultSetMetadata for the RowSet
1464      * @return true if found, false otherwise (auto generated key)
1465      */
1466     private boolean isPKNameValid(String pk, ResultSetMetaData rsmd) throws SQLException {
1467         boolean isValid = false;
1468         int cols = rsmd.getColumnCount();
1469         for(int i = 1; i<= cols; i++) {
1470             String colName = rsmd.getColumnClassName(i);
1471             if(colName.equalsIgnoreCase(pk)) {
1472                 isValid = true;
1473                 break;
1474             }
1475         }   
1476         
1477         return isValid;
1478     }
1479 }