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