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