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