1 /*
   2  * Copyright (c) 2003, 2010, 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         int i = 0;
 832         int icolCount = crs.getMetaData().getColumnCount();
 833 
 834         boolean returnVal = false;
 835         PreparedStatement pstmtSel = con.prepareStatement(selectCmd,
 836                         ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
 837         ResultSet rs, rs2 = null;
 838         DatabaseMetaData dbmd = con.getMetaData();
 839         rs = pstmtSel.executeQuery();
 840         String table = crs.getTableName();
 841         rs2 = dbmd.getPrimaryKeys(null, null, table);
 842         String [] primaryKeys = new String[icolCount];
 843         int k = 0;
 844         while(rs2.next()) {
 845             String pkcolname = rs2.getString("COLUMN_NAME");
 846             primaryKeys[k] = pkcolname;
 847             k++;
 848         }
 849 
 850         if(rs.next()) {
 851             for(int j=0;j<primaryKeys.length;j++) {
 852                 if(primaryKeys[j] != null) {
 853                     if(crs.getObject(primaryKeys[j]) == null){
 854                         break;
 855                     }
 856                     String crsPK = (crs.getObject(primaryKeys[j])).toString();
 857                     String rsPK = (rs.getObject(primaryKeys[j])).toString();
 858                     if(crsPK.equals(rsPK)) {
 859                         returnVal = true;
 860                         this.crsResolve.moveToInsertRow();
 861                         for(i = 1; i <= icolCount; i++) {
 862                             String colname = (rs.getMetaData()).getColumnName(i);
 863                             if(colname.equals(primaryKeys[j]))
 864                                 this.crsResolve.updateObject(i,rsPK);
 865                             else
 866                                 this.crsResolve.updateNull(i);
 867                         }
 868                         this.crsResolve.insertRow();
 869                         this.crsResolve.moveToCurrentRow();
 870                     }
 871                 }
 872             }
 873         }
 874         if(returnVal)
 875             return returnVal;
 876 
 877         try {
 878             for (i = 1; i <= icolCount; i++) {
 879                 Object obj = crs.getObject(i);
 880                 if (obj != null) {
 881                     pstmt.setObject(i, obj);
 882                 } else {
 883                     pstmt.setNull(i,crs.getMetaData().getColumnType(i));
 884                 }
 885             }
 886 
 887              i = pstmt.executeUpdate();
 888              return false;
 889 
 890         } catch (SQLException ex) {
 891             /**
 892              * Cursor will come here if executeUpdate fails.
 893              * There can be many reasons why the insertion failed,
 894              * one can be violation of primary key.
 895              * Hence we cannot exactly identify why the insertion failed
 896              * Present the current row as a null row to the user.
 897              **/
 898             this.crsResolve.moveToInsertRow();
 899 
 900             for(i = 1; i <= icolCount; i++) {
 901                this.crsResolve.updateNull(i);
 902             }
 903 
 904             this.crsResolve.insertRow();
 905             this.crsResolve.moveToCurrentRow();
 906 
 907             return true;
 908         }
 909     }
 910 
 911 /**
 912  * Deletes the row in the underlying data source that corresponds to
 913  * a row that has been deleted in the given <code> CachedRowSet</code> object
 914  * and returns <code>false</code> if the deletion was successful.
 915  * <P>
 916  * This method is called internally by this writer's <code>writeData</code>
 917  * method when a row in the rowset has been deleted. The values in the
 918  * deleted row are the same as those that are stored in the original row
 919  * of the given <code>CachedRowSet</code> object.  If the values in the
 920  * original row differ from the row in the underlying data source, the row
 921  * in the data source is not deleted, and <code>deleteOriginalRow</code>
 922  * returns <code>true</code> to indicate that there was a conflict.
 923  *
 924  *
 925  * @return <code>false</code> if the deletion was successful, which means that
 926  *         there was no conflict; <code>true</code> otherwise
 927  * @throws SQLException if there was a database access error
 928  */
 929     private boolean deleteOriginalRow(CachedRowSet crs, CachedRowSetImpl crsRes) throws SQLException {
 930         PreparedStatement pstmt;
 931         int i;
 932         int idx = 0;
 933         String strSelect;
 934     // Select the row from the database.
 935         ResultSet origVals = crs.getOriginalRow();
 936         origVals.next();
 937 
 938         deleteWhere = buildWhereClause(deleteWhere, origVals);
 939         pstmt = con.prepareStatement(selectCmd + deleteWhere,
 940                 ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
 941 
 942         for (i = 0; i < keyCols.length; i++) {
 943             if (params[i] != null) {
 944                 pstmt.setObject(++idx, params[i]);
 945             } else {
 946                 continue;
 947             }
 948         }
 949 
 950         try {
 951             pstmt.setMaxRows(crs.getMaxRows());
 952             pstmt.setMaxFieldSize(crs.getMaxFieldSize());
 953             pstmt.setEscapeProcessing(crs.getEscapeProcessing());
 954             pstmt.setQueryTimeout(crs.getQueryTimeout());
 955         } catch (Exception ex) {
 956             /*
 957              * Older driver don't support these operations...
 958              */
 959             ;
 960         }
 961 
 962         ResultSet rs = pstmt.executeQuery();
 963 
 964         if (rs.next() == true) {
 965             if (rs.next()) {
 966                 // more than one row
 967                 return true;
 968             }
 969             rs.first();
 970 
 971             // Now check all the values in rs to be same in
 972             // db also before actually going ahead with deleting
 973             boolean boolChanged = false;
 974 
 975             crsRes.moveToInsertRow();
 976 
 977             for (i = 1; i <= crs.getMetaData().getColumnCount(); i++) {
 978 
 979                 Object original = origVals.getObject(i);
 980                 Object changed = rs.getObject(i);
 981 
 982                 if(original != null && changed != null ) {
 983                   if(! (original.toString()).equals(changed.toString()) ) {
 984                       boolChanged = true;
 985                       crsRes.updateObject(i,origVals.getObject(i));
 986                   }
 987                 } else {
 988                    crsRes.updateNull(i);
 989                }
 990             }
 991 
 992            crsRes.insertRow();
 993            crsRes.moveToCurrentRow();
 994 
 995            if(boolChanged) {
 996                // do not delete as values in db have changed
 997                // deletion will not happen for this row from db
 998                    // exit now returning true. i.e. conflict
 999                return true;
1000             } else {
1001                 // delete the row.
1002                 // Go ahead with deleting,
1003                 // don't do anything here
1004             }
1005 
1006             String cmd = deleteCmd + deleteWhere;
1007             pstmt = con.prepareStatement(cmd);
1008 
1009             idx = 0;
1010             for (i = 0; i < keyCols.length; i++) {
1011                 if (params[i] != null) {
1012                     pstmt.setObject(++idx, params[i]);
1013                 } else {
1014                     continue;
1015                 }
1016             }
1017 
1018             if (pstmt.executeUpdate() != 1) {
1019                 return true;
1020             }
1021             pstmt.close();
1022         } else {
1023             // didn't find the row
1024             return true;
1025         }
1026 
1027         // no conflict
1028         return false;
1029     }
1030 
1031     /**
1032      * Sets the reader for this writer to the given reader.
1033      *
1034      * @throws SQLException if a database access error occurs
1035      */
1036     public void setReader(CachedRowSetReader reader) throws SQLException {
1037         this.reader = reader;
1038     }
1039 
1040     /**
1041      * Gets the reader for this writer.
1042      *
1043      * @throws SQLException if a database access error occurs
1044      */
1045     public CachedRowSetReader getReader() throws SQLException {
1046         return reader;
1047     }
1048 
1049     /**
1050      * Composes a <code>SELECT</code>, <code>UPDATE</code>, <code>INSERT</code>,
1051      * and <code>DELETE</code> statement that can be used by this writer to
1052      * write data to the data source backing the given <code>CachedRowSet</code>
1053      * object.
1054      *
1055      * @ param caller a <code>CachedRowSet</code> object for which this
1056      *                <code>CachedRowSetWriter</code> object is the writer
1057      * @throws SQLException if a database access error occurs
1058      */
1059     private void initSQLStatements(CachedRowSet caller) throws SQLException {
1060 
1061         int i;
1062 
1063         callerMd = caller.getMetaData();
1064         callerColumnCount = callerMd.getColumnCount();
1065         if (callerColumnCount < 1)
1066             // No data, so return.
1067             return;
1068 
1069         /*
1070          * If the RowSet has a Table name we should use it.
1071          * This is really a hack to get round the fact that
1072          * a lot of the jdbc drivers can't provide the tab.
1073          */
1074         String table = caller.getTableName();
1075         if (table == null) {
1076             /*
1077              * attempt to build a table name using the info
1078              * that the driver gave us for the first column
1079              * in the source result set.
1080              */
1081             table = callerMd.getTableName(1);
1082             if (table == null || table.length() == 0) {
1083                 throw new SQLException(resBundle.handleGetObject("crswriter.tname").toString());
1084             }
1085         }
1086         String catalog = callerMd.getCatalogName(1);
1087             String schema = callerMd.getSchemaName(1);
1088         DatabaseMetaData dbmd = con.getMetaData();
1089 
1090         /*
1091          * Compose a SELECT statement.  There are three parts.
1092          */
1093 
1094         // Project List
1095         selectCmd = "SELECT ";
1096         for (i=1; i <= callerColumnCount; i++) {
1097             selectCmd += callerMd.getColumnName(i);
1098             if ( i <  callerMd.getColumnCount() )
1099                 selectCmd += ", ";
1100             else
1101                 selectCmd += " ";
1102         }
1103 
1104         // FROM clause.
1105         selectCmd += "FROM " + buildTableName(dbmd, catalog, schema, table);
1106 
1107         /*
1108          * Compose an UPDATE statement.
1109          */
1110         updateCmd = "UPDATE " + buildTableName(dbmd, catalog, schema, table);
1111 
1112 
1113         /**
1114          *  The following block of code is for checking a particular type of
1115          *  query where in there is a where clause. Without this block, if a
1116          *  SQL statement is built the "where" clause will appear twice hence
1117          *  the DB errors out and a SQLException is thrown. This code also
1118          *  considers that the where clause is in the right place as the
1119          *  CachedRowSet object would already have been populated with this
1120          *  query before coming to this point.
1121          **/
1122 
1123         String tempupdCmd = updateCmd.toLowerCase();
1124 
1125         int idxupWhere = tempupdCmd.indexOf("where");
1126 
1127         if(idxupWhere != -1)
1128         {
1129            updateCmd = updateCmd.substring(0,idxupWhere);
1130         }
1131         updateCmd += "SET ";
1132 
1133         /*
1134          * Compose an INSERT statement.
1135          */
1136         insertCmd = "INSERT INTO " + buildTableName(dbmd, catalog, schema, table);
1137         // Column list
1138         insertCmd += "(";
1139         for (i=1; i <= callerColumnCount; i++) {
1140             insertCmd += callerMd.getColumnName(i);
1141             if ( i <  callerMd.getColumnCount() )
1142                 insertCmd += ", ";
1143             else
1144                 insertCmd += ") VALUES (";
1145         }
1146         for (i=1; i <= callerColumnCount; i++) {
1147             insertCmd += "?";
1148             if (i < callerColumnCount)
1149                 insertCmd += ", ";
1150             else
1151                 insertCmd += ")";
1152         }
1153 
1154         /*
1155          * Compose a DELETE statement.
1156          */
1157         deleteCmd = "DELETE FROM " + buildTableName(dbmd, catalog, schema, table);
1158 
1159         /*
1160          * set the key desriptors that will be
1161          * needed to construct where clauses.
1162          */
1163         buildKeyDesc(caller);
1164     }
1165 
1166     /**
1167      * Returns a fully qualified table name built from the given catalog and
1168      * table names. The given metadata object is used to get the proper order
1169      * and separator.
1170      *
1171      * @param dbmd a <code>DatabaseMetaData</code> object that contains metadata
1172      *          about this writer's <code>CachedRowSet</code> object
1173      * @param catalog a <code>String</code> object with the rowset's catalog
1174      *          name
1175      * @param table a <code>String</code> object with the name of the table from
1176      *          which this writer's rowset was derived
1177      * @return a <code>String</code> object with the fully qualified name of the
1178      *          table from which this writer's rowset was derived
1179      * @throws SQLException if a database access error occurs
1180      */
1181     private String buildTableName(DatabaseMetaData dbmd,
1182         String catalog, String schema, String table) throws SQLException {
1183 
1184        // trim all the leading and trailing whitespaces,
1185        // white spaces can never be catalog, schema or a table name.
1186 
1187         String cmd = "";
1188 
1189         catalog = catalog.trim();
1190         schema = schema.trim();
1191         table = table.trim();
1192 
1193         if (dbmd.isCatalogAtStart() == true) {
1194             if (catalog != null && catalog.length() > 0) {
1195                 cmd += catalog + dbmd.getCatalogSeparator();
1196             }
1197             if (schema != null && schema.length() > 0) {
1198                 cmd += schema + ".";
1199             }
1200             cmd += table;
1201         } else {
1202             if (schema != null && schema.length() > 0) {
1203                 cmd += schema + ".";
1204             }
1205             cmd += table;
1206             if (catalog != null && catalog.length() > 0) {
1207                 cmd += dbmd.getCatalogSeparator() + catalog;
1208             }
1209         }
1210         cmd += " ";
1211         return cmd;
1212     }
1213 
1214     /**
1215      * Assigns to the given <code>CachedRowSet</code> object's
1216      * <code>params</code>
1217      * field an array whose length equals the number of columns needed
1218      * to uniquely identify a row in the rowset. The array is given
1219      * values by the method <code>buildWhereClause</code>.
1220      * <P>
1221      * If the <code>CachedRowSet</code> object's <code>keyCols</code>
1222      * field has length <code>0</code> or is <code>null</code>, the array
1223      * is set with the column number of every column in the rowset.
1224      * Otherwise, the array in the field <code>keyCols</code> is set with only
1225      * the column numbers of the columns that are required to form a unique
1226      * identifier for a row.
1227      *
1228      * @param crs the <code>CachedRowSet</code> object for which this
1229      *     <code>CachedRowSetWriter</code> object is the writer
1230      *
1231      * @throws SQLException if a database access error occurs
1232      */
1233     private void buildKeyDesc(CachedRowSet crs) throws SQLException {
1234 
1235         keyCols = crs.getKeyColumns();
1236         ResultSetMetaData resultsetmd = crs.getMetaData();
1237         if (keyCols == null || keyCols.length == 0) {
1238             ArrayList<Integer> listKeys = new ArrayList<Integer>();
1239 
1240             for (int i = 0; i < callerColumnCount; i++ ) {
1241                 if(resultsetmd.getColumnType(i+1) != java.sql.Types.CLOB &&
1242                         resultsetmd.getColumnType(i+1) != java.sql.Types.STRUCT &&
1243                         resultsetmd.getColumnType(i+1) != java.sql.Types.SQLXML &&
1244                         resultsetmd.getColumnType(i+1) != java.sql.Types.BLOB &&
1245                         resultsetmd.getColumnType(i+1) != java.sql.Types.ARRAY &&
1246                         resultsetmd.getColumnType(i+1) != java.sql.Types.OTHER )
1247                     listKeys.add(i+1);
1248             }
1249             keyCols = new int[listKeys.size()];
1250             for (int i = 0; i < listKeys.size(); i++ )
1251                 keyCols[i] = listKeys.get(i);
1252         }
1253         params = new Object[keyCols.length];
1254     }
1255 
1256     /**
1257          * Constructs an SQL <code>WHERE</code> clause using the given
1258          * string as a starting point. The resulting clause will contain
1259          * a column name and " = ?" for each key column, that is, each column
1260          * that is needed to form a unique identifier for a row in the rowset.
1261          * This <code>WHERE</code> clause can be added to
1262          * a <code>PreparedStatement</code> object that updates, inserts, or
1263          * deletes a row.
1264          * <P>
1265          * This method uses the given result set to access values in the
1266          * <code>CachedRowSet</code> object that called this writer.  These
1267          * values are used to build the array of parameters that will serve as
1268          * replacements for the "?" parameter placeholders in the
1269          * <code>PreparedStatement</code> object that is sent to the
1270          * <code>CachedRowSet</code> object's underlying data source.
1271          *
1272          * @param whereClause a <code>String</code> object that is an empty
1273          *                    string ("")
1274          * @param rs a <code>ResultSet</code> object that can be used
1275          *           to access the <code>CachedRowSet</code> object's data
1276          * @return a <code>WHERE</code> clause of the form "<code>WHERE</code>
1277          *         columnName = ? AND columnName = ? AND columnName = ? ..."
1278          * @throws SQLException if a database access error occurs
1279          */
1280     private String buildWhereClause(String whereClause,
1281                                     ResultSet rs) throws SQLException {
1282         whereClause = "WHERE ";
1283 
1284         for (int i = 0; i < keyCols.length; i++) {
1285             if (i > 0) {
1286                     whereClause += "AND ";
1287             }
1288             whereClause += callerMd.getColumnName(keyCols[i]);
1289             params[i] = rs.getObject(keyCols[i]);
1290             if (rs.wasNull() == true) {
1291                 whereClause += " IS NULL ";
1292             } else {
1293                 whereClause += " = ? ";
1294             }
1295         }
1296         return whereClause;
1297     }
1298 
1299     void updateResolvedConflictToDB(CachedRowSet crs, Connection con) throws SQLException {
1300           //String updateExe = ;
1301           PreparedStatement pStmt  ;
1302           String strWhere = "WHERE " ;
1303           String strExec =" ";
1304           String strUpdate = "UPDATE ";
1305           int icolCount = crs.getMetaData().getColumnCount();
1306           int keyColumns[] = crs.getKeyColumns();
1307           Object param[];
1308           String strSet="";
1309 
1310         strWhere = buildWhereClause(strWhere, crs);
1311 
1312         if (keyColumns == null || keyColumns.length == 0) {
1313             keyColumns = new int[icolCount];
1314             for (int i = 0; i < keyColumns.length; ) {
1315                 keyColumns[i] = ++i;
1316             }
1317           }
1318           param = new Object[keyColumns.length];
1319 
1320          strUpdate = "UPDATE " + buildTableName(con.getMetaData(),
1321                             crs.getMetaData().getCatalogName(1),
1322                            crs.getMetaData().getSchemaName(1),
1323                            crs.getTableName());
1324 
1325          // changed or updated values will become part of
1326          // set clause here
1327          strUpdate += "SET ";
1328 
1329         boolean first = true;
1330 
1331         for (int i=1; i<=icolCount;i++) {
1332            if (crs.columnUpdated(i)) {
1333                   if (first == false) {
1334                     strSet += ", ";
1335                   }
1336                  strSet += crs.getMetaData().getColumnName(i);
1337                  strSet += " = ? ";
1338                  first = false;
1339          } //end if
1340       } //end for
1341 
1342          // keycols will become part of where clause
1343          strUpdate += strSet;
1344          strWhere = "WHERE ";
1345 
1346         for (int i = 0; i < keyColumns.length; i++) {
1347             if (i > 0) {
1348                     strWhere += "AND ";
1349             }
1350             strWhere += crs.getMetaData().getColumnName(keyColumns[i]);
1351             param[i] = crs.getObject(keyColumns[i]);
1352             if (crs.wasNull() == true) {
1353                 strWhere += " IS NULL ";
1354             } else {
1355                 strWhere += " = ? ";
1356             }
1357         }
1358           strUpdate += strWhere;
1359 
1360         pStmt = con.prepareStatement(strUpdate);
1361 
1362         int idx =0;
1363           for (int i = 0; i < icolCount; i++) {
1364              if(crs.columnUpdated(i+1)) {
1365               Object obj = crs.getObject(i+1);
1366               if (obj != null) {
1367                   pStmt.setObject(++idx, obj);
1368               } else {
1369                   pStmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));
1370              } //end if ..else
1371            } //end if crs.column...
1372         } //end for
1373 
1374           // Set the key cols for after WHERE =? clause
1375           for (int i = 0; i < keyColumns.length; i++) {
1376               if (param[i] != null) {
1377                   pStmt.setObject(++idx, param[i]);
1378               }
1379           }
1380 
1381         int id = pStmt.executeUpdate();
1382       }
1383 
1384 
1385     /**
1386      *
1387      */
1388     public void commit() throws SQLException {
1389         con.commit();
1390         if (reader.getCloseConnection() == true) {
1391             con.close();
1392         }
1393     }
1394 
1395      public void commit(CachedRowSetImpl crs, boolean updateRowset) throws SQLException {
1396         con.commit();
1397         if(updateRowset) {
1398           if(crs.getCommand() != null)
1399             crs.execute(con);
1400         }
1401 
1402         if (reader.getCloseConnection() == true) {
1403             con.close();
1404         }
1405     }
1406 
1407     /**
1408      *
1409      */
1410     public void rollback() throws SQLException {
1411         con.rollback();
1412         if (reader.getCloseConnection() == true) {
1413             con.close();
1414         }
1415     }
1416 
1417     /**
1418      *
1419      */
1420     public void rollback(Savepoint s) throws SQLException {
1421         con.rollback(s);
1422         if (reader.getCloseConnection() == true) {
1423             con.close();
1424         }
1425     }
1426 
1427     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
1428         // Default state initialization happens here
1429         ois.defaultReadObject();
1430         // Initialization of  Res Bundle happens here .
1431         try {
1432            resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
1433         } catch(IOException ioe) {
1434             throw new RuntimeException(ioe);
1435         }
1436 
1437     }
1438 
1439     static final long serialVersionUID =-8506030970299413976L;
1440 }