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