0001: /*
0002: * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package com.sun.rowset.internal;
0027:
0028: import java.sql.*;
0029: import javax.sql.*;
0030: import java.util.*;
0031: import java.io.*;
0032:
0033: import com.sun.rowset.*;
0034: import javax.sql.rowset.*;
0035: import javax.sql.rowset.spi.*;
0036:
0037: /**
0038: * The facility called on internally by the <code>RIOptimisticProvider</code> implementation to
0039: * propagate changes back to the data source from which the rowset got its data.
0040: * <P>
0041: * A <code>CachedRowSetWriter</code> object, called a writer, has the public
0042: * method <code>writeData</code> for writing modified data to the underlying data source.
0043: * This method is invoked by the rowset internally and is never invoked directly by an application.
0044: * A writer also has public methods for setting and getting
0045: * the <code>CachedRowSetReader</code> object, called a reader, that is associated
0046: * with the writer. The remainder of the methods in this class are private and
0047: * are invoked internally, either directly or indirectly, by the method
0048: * <code>writeData</code>.
0049: * <P>
0050: * Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and
0051: * the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects.
0052: * Standard JDBC RowSet implementations provide an object instance of this
0053: * writer by invoking the <code>SyncProvider.getRowSetWriter()</code> method.
0054: *
0055: * @version 0.2
0056: * @author Jonathan Bruce
0057: * @see javax.sql.rowset.spi.SyncProvider
0058: * @see javax.sql.rowset.spi.SyncFactory
0059: * @see javax.sql.rowset.spi.SyncFactoryException
0060: */
0061: public class CachedRowSetWriter implements TransactionalWriter,
0062: Serializable {
0063:
0064: /**
0065: * The <code>Connection</code> object that this writer will use to make a
0066: * connection to the data source to which it will write data.
0067: *
0068: */
0069: private transient Connection con;
0070:
0071: /**
0072: * The SQL <code>SELECT</code> command that this writer will call
0073: * internally. The method <code>initSQLStatements</code> builds this
0074: * command by supplying the words "SELECT" and "FROM," and using
0075: * metadata to get the table name and column names .
0076: *
0077: * @serial
0078: */
0079: private String selectCmd;
0080:
0081: /**
0082: * The SQL <code>UPDATE</code> command that this writer will call
0083: * internally to write data to the rowset's underlying data source.
0084: * The method <code>initSQLStatements</code> builds this <code>String</code>
0085: * object.
0086: *
0087: * @serial
0088: */
0089: private String updateCmd;
0090:
0091: /**
0092: * The SQL <code>WHERE</code> clause the writer will use for update
0093: * statements in the <code>PreparedStatement</code> object
0094: * it sends to the underlying data source.
0095: *
0096: * @serial
0097: */
0098: private String updateWhere;
0099:
0100: /**
0101: * The SQL <code>DELETE</code> command that this writer will call
0102: * internally to delete a row in the rowset's underlying data source.
0103: *
0104: * @serial
0105: */
0106: private String deleteCmd;
0107:
0108: /**
0109: * The SQL <code>WHERE</code> clause the writer will use for delete
0110: * statements in the <code>PreparedStatement</code> object
0111: * it sends to the underlying data source.
0112: *
0113: * @serial
0114: */
0115: private String deleteWhere;
0116:
0117: /**
0118: * The SQL <code>INSERT INTO</code> command that this writer will internally use
0119: * to insert data into the rowset's underlying data source. The method
0120: * <code>initSQLStatements</code> builds this command with a question
0121: * mark parameter placeholder for each column in the rowset.
0122: *
0123: * @serial
0124: */
0125: private String insertCmd;
0126:
0127: /**
0128: * An array containing the column numbers of the columns that are
0129: * needed to uniquely identify a row in the <code>CachedRowSet</code> object
0130: * for which this <code>CachedRowSetWriter</code> object is the writer.
0131: *
0132: * @serial
0133: */
0134: private int[] keyCols;
0135:
0136: /**
0137: * An array of the parameters that should be used to set the parameter
0138: * placeholders in a <code>PreparedStatement</code> object that this
0139: * writer will execute.
0140: *
0141: * @serial
0142: */
0143: private Object[] params;
0144:
0145: /**
0146: * The <code>CachedRowSetReader</code> object that has been
0147: * set as the reader for the <code>CachedRowSet</code> object
0148: * for which this <code>CachedRowSetWriter</code> object is the writer.
0149: *
0150: * @serial
0151: */
0152: private CachedRowSetReader reader;
0153:
0154: /**
0155: * The <code>ResultSetMetaData</code> object that contains information
0156: * about the columns in the <code>CachedRowSet</code> object
0157: * for which this <code>CachedRowSetWriter</code> object is the writer.
0158: *
0159: * @serial
0160: */
0161: private ResultSetMetaData callerMd;
0162:
0163: /**
0164: * The number of columns in the <code>CachedRowSet</code> object
0165: * for which this <code>CachedRowSetWriter</code> object is the writer.
0166: *
0167: * @serial
0168: */
0169: private int callerColumnCount;
0170:
0171: /**
0172: * This <code>CachedRowSet<code> will hold the conflicting values
0173: * retrieved from the db and hold it.
0174: */
0175: private CachedRowSetImpl crsResolve;
0176:
0177: /**
0178: * This <code>ArrayList<code> will hold the values of SyncResolver.*
0179: */
0180: private ArrayList status;
0181:
0182: /**
0183: * This will check whether the same field value has changed both
0184: * in database and CachedRowSet.
0185: */
0186: private int iChangedValsInDbAndCRS;
0187:
0188: /**
0189: * This will hold the number of cols for which the values have
0190: * changed only in database.
0191: */
0192: private int iChangedValsinDbOnly;
0193:
0194: private JdbcRowSetResourceBundle resBundle;
0195:
0196: public CachedRowSetWriter() {
0197: try {
0198: resBundle = JdbcRowSetResourceBundle
0199: .getJdbcRowSetResourceBundle();
0200: } catch (IOException ioe) {
0201: throw new RuntimeException(ioe);
0202: }
0203: }
0204:
0205: /**
0206: * Propagates changes in the given <code>RowSet</code> object
0207: * back to its underlying data source and returns <code>true</code>
0208: * if successful. The writer will check to see if
0209: * the data in the pre-modified rowset (the original values) differ
0210: * from the data in the underlying data source. If data in the data
0211: * source has been modified by someone else, there is a conflict,
0212: * and in that case, the writer will not write to the data source.
0213: * In other words, the writer uses an optimistic concurrency algorithm:
0214: * It checks for conflicts before making changes rather than restricting
0215: * access for concurrent users.
0216: * <P>
0217: * This method is called by the rowset internally when
0218: * the application invokes the method <code>acceptChanges</code>.
0219: * The <code>writeData</code> method in turn calls private methods that
0220: * it defines internally.
0221: * The following is a general summary of what the method
0222: * <code>writeData</code> does, much of which is accomplished
0223: * through calls to its own internal methods.
0224: * <OL>
0225: * <LI>Creates a <code>CachedRowSet</code> object from the given
0226: * <code>RowSet</code> object
0227: * <LI>Makes a connection with the data source
0228: * <UL>
0229: * <LI>Disables autocommit mode if it is not already disabled
0230: * <LI>Sets the transaction isolation level to that of the rowset
0231: * </UL>
0232: * <LI>Checks to see if the reader has read new data since the writer
0233: * was last called and, if so, calls the method
0234: * <code>initSQLStatements</code> to initialize new SQL statements
0235: * <UL>
0236: * <LI>Builds new <code>SELECT</code>, <code>UPDATE</code>,
0237: * <code>INSERT</code>, and <code>DELETE</code> statements
0238: * <LI>Uses the <code>CachedRowSet</code> object's metadata to
0239: * determine the table name, column names, and the columns
0240: * that make up the primary key
0241: * </UL>
0242: * <LI>When there is no conflict, propagates changes made to the
0243: * <code>CachedRowSet</code> object back to its underlying data source
0244: * <UL>
0245: * <LI>Iterates through each row of the <code>CachedRowSet</code> object
0246: * to determine whether it has been updated, inserted, or deleted
0247: * <LI>If the corresponding row in the data source has not been changed
0248: * since the rowset last read its
0249: * values, the writer will use the appropriate command to update,
0250: * insert, or delete the row
0251: * <LI>If any data in the data source does not match the original values
0252: * for the <code>CachedRowSet</code> object, the writer will roll
0253: * back any changes it has made to the row in the data source.
0254: * </UL>
0255: * </OL>
0256: *
0257: * @return <code>true</code> if changes to the rowset were successfully
0258: * written to the rowset's underlying data source;
0259: * <code>false</code> otherwise
0260: */
0261: public boolean writeData(RowSetInternal caller) throws SQLException {
0262: boolean conflict = false;
0263: boolean showDel = false;
0264: PreparedStatement pstmtIns = null;
0265: iChangedValsInDbAndCRS = 0;
0266: iChangedValsinDbOnly = 0;
0267:
0268: // We assume caller is a CachedRowSet
0269: CachedRowSetImpl crs = (CachedRowSetImpl) caller;
0270: // crsResolve = new CachedRowSetImpl();
0271: this .crsResolve = new CachedRowSetImpl();
0272: ;
0273:
0274: // The reader is registered with the writer at design time.
0275: // This is not required, in general. The reader has logic
0276: // to get a JDBC connection, so call it.
0277:
0278: con = reader.connect(caller);
0279:
0280: if (con == null) {
0281: throw new SQLException(resBundle.handleGetObject(
0282: "crswriter.connect").toString());
0283: }
0284:
0285: /*
0286: // Fix 6200646.
0287: // Don't change the connection or transaction properties. This will fail in a
0288: // J2EE container.
0289: if (con.getAutoCommit() == true) {
0290: con.setAutoCommit(false);
0291: }
0292:
0293: con.setTransactionIsolation(crs.getTransactionIsolation());
0294: */
0295:
0296: initSQLStatements(crs);
0297: int iColCount;
0298:
0299: RowSetMetaDataImpl rsmdWrite = (RowSetMetaDataImpl) crs
0300: .getMetaData();
0301: RowSetMetaDataImpl rsmdResolv = new RowSetMetaDataImpl();
0302:
0303: iColCount = rsmdWrite.getColumnCount();
0304: int sz = crs.size() + 1;
0305: status = new ArrayList(sz);
0306:
0307: status.add(0, null);
0308: rsmdResolv.setColumnCount(iColCount);
0309:
0310: for (int i = 1; i <= iColCount; i++) {
0311: rsmdResolv.setColumnType(i, rsmdWrite.getColumnType(i));
0312: rsmdResolv.setColumnName(i, rsmdWrite.getColumnName(i));
0313: rsmdResolv.setNullable(i,
0314: ResultSetMetaData.columnNullableUnknown);
0315: }
0316: this .crsResolve.setMetaData(rsmdResolv);
0317:
0318: // moved outside the insert inner loop
0319: //pstmtIns = con.prepareStatement(insertCmd);
0320:
0321: if (callerColumnCount < 1) {
0322: // No data, so return success.
0323: if (reader.getCloseConnection() == true)
0324: con.close();
0325: return true;
0326: }
0327: // We need to see rows marked for deletion.
0328: showDel = crs.getShowDeleted();
0329: crs.setShowDeleted(true);
0330:
0331: // Look at all the rows.
0332: crs.beforeFirst();
0333:
0334: int rows = 1;
0335: while (crs.next()) {
0336: if (crs.rowDeleted()) {
0337: // The row has been deleted.
0338: if (conflict = (deleteOriginalRow(crs, this .crsResolve)) == true) {
0339: status.add(rows, new Integer(
0340: SyncResolver.DELETE_ROW_CONFLICT));
0341: } else {
0342: // delete happened without any occurrence of conflicts
0343: // so update status accordingly
0344: status.add(rows, new Integer(
0345: SyncResolver.NO_ROW_CONFLICT));
0346: }
0347:
0348: } else if (crs.rowInserted()) {
0349: // The row has been inserted.
0350:
0351: pstmtIns = con.prepareStatement(insertCmd);
0352: if ((conflict = insertNewRow(crs, pstmtIns,
0353: this .crsResolve)) == true) {
0354: status.add(rows, new Integer(
0355: SyncResolver.INSERT_ROW_CONFLICT));
0356: } else {
0357: // insert happened without any occurrence of conflicts
0358: // so update status accordingly
0359: status.add(rows, new Integer(
0360: SyncResolver.NO_ROW_CONFLICT));
0361: }
0362: } else if (crs.rowUpdated()) {
0363: // The row has been updated.
0364: if (conflict = (updateOriginalRow(crs)) == true) {
0365: status.add(rows, new Integer(
0366: SyncResolver.UPDATE_ROW_CONFLICT));
0367: } else {
0368: // update happened without any occurrence of conflicts
0369: // so update status accordingly
0370: status.add(rows, new Integer(
0371: SyncResolver.NO_ROW_CONFLICT));
0372: }
0373:
0374: } else {
0375: /** The row is neither of inserted, updated or deleted.
0376: * So set nulls in the this.crsResolve for this row,
0377: * as nothing is to be done for such rows.
0378: * Also note that if such a row has been changed in database
0379: * and we have not changed(inserted, updated or deleted)
0380: * that is fine.
0381: **/
0382: int icolCount = crs.getMetaData().getColumnCount();
0383: status.add(rows, new Integer(
0384: SyncResolver.NO_ROW_CONFLICT));
0385:
0386: this .crsResolve.moveToInsertRow();
0387: for (int cols = 0; cols < iColCount; cols++) {
0388: this .crsResolve.updateNull(cols + 1);
0389: } //end for
0390:
0391: this .crsResolve.insertRow();
0392: this .crsResolve.moveToCurrentRow();
0393:
0394: } //end if
0395: rows++;
0396: } //end while
0397:
0398: // close the insert statement
0399: if (pstmtIns != null)
0400: pstmtIns.close();
0401: // reset
0402: crs.setShowDeleted(showDel);
0403:
0404: boolean boolConf = false;
0405: for (int j = 1; j < status.size(); j++) {
0406: // ignore status for index = 0 which is set to null
0407: if (!((status.get(j)).equals(new Integer(
0408: SyncResolver.NO_ROW_CONFLICT)))) {
0409: // there is at least one conflict which needs to be resolved
0410: boolConf = true;
0411: break;
0412: }
0413: }
0414:
0415: crs.beforeFirst();
0416: this .crsResolve.beforeFirst();
0417:
0418: if (boolConf) {
0419: SyncProviderException spe = new SyncProviderException(
0420: status.size()
0421: - 1
0422: + resBundle.handleGetObject(
0423: "crswriter.conflictsno").toString());
0424: //SyncResolver syncRes = spe.getSyncResolver();
0425:
0426: SyncResolverImpl syncResImpl = (SyncResolverImpl) spe
0427: .getSyncResolver();
0428:
0429: syncResImpl.setCachedRowSet(crs);
0430: syncResImpl.setCachedRowSetResolver(this .crsResolve);
0431:
0432: syncResImpl.setStatus(status);
0433: syncResImpl.setCachedRowSetWriter(this );
0434:
0435: throw spe;
0436: } else {
0437: return true;
0438: }
0439: /*
0440: if (conflict == true) {
0441: con.rollback();
0442: return false;
0443: } else {
0444: con.commit();
0445: if (reader.getCloseConnection() == true) {
0446: con.close();
0447: }
0448: return true;
0449: }
0450: */
0451:
0452: } //end writeData
0453:
0454: /**
0455: * Updates the given <code>CachedRowSet</code> object's underlying data
0456: * source so that updates to the rowset are reflected in the original
0457: * data source, and returns <code>false</code> if the update was successful.
0458: * A return value of <code>true</code> indicates that there is a conflict,
0459: * meaning that a value updated in the rowset has already been changed by
0460: * someone else in the underlying data source. A conflict can also exist
0461: * if, for example, more than one row in the data source would be affected
0462: * by the update or if no rows would be affected. In any case, if there is
0463: * a conflict, this method does not update the underlying data source.
0464: * <P>
0465: * This method is called internally by the method <code>writeData</code>
0466: * if a row in the <code>CachedRowSet</code> object for which this
0467: * <code>CachedRowSetWriter</code> object is the writer has been updated.
0468: *
0469: * @return <code>false</code> if the update to the underlying data source is
0470: * successful; <code>true</code> otherwise
0471: * @throws SQLException if a database access error occurs
0472: */
0473: private boolean updateOriginalRow(CachedRowSet crs)
0474: throws SQLException {
0475: PreparedStatement pstmt;
0476: int i = 0;
0477: int idx = 0;
0478:
0479: // Select the row from the database.
0480: ResultSet origVals = crs.getOriginalRow();
0481: origVals.next();
0482:
0483: try {
0484: updateWhere = buildWhereClause(updateWhere, origVals);
0485:
0486: /**
0487: * The following block of code is for checking a particular type of
0488: * query where in there is a where clause. Without this block, if a
0489: * SQL statement is built the "where" clause will appear twice hence
0490: * the DB errors out and a SQLException is thrown. This code also
0491: * considers that the where clause is in the right place as the
0492: * CachedRowSet object would already have been populated with this
0493: * query before coming to this point.
0494: **/
0495:
0496: String tempselectCmd = selectCmd.toLowerCase();
0497:
0498: int idxWhere = tempselectCmd.indexOf("where");
0499:
0500: if (idxWhere != -1) {
0501: String tempSelect = selectCmd.substring(0, idxWhere);
0502: selectCmd = tempSelect;
0503: }
0504:
0505: pstmt = con.prepareStatement(selectCmd + updateWhere,
0506: ResultSet.TYPE_SCROLL_SENSITIVE,
0507: ResultSet.CONCUR_READ_ONLY);
0508:
0509: for (i = 0; i < keyCols.length; i++) {
0510: if (params[i] != null) {
0511: pstmt.setObject(++idx, params[i]);
0512: } else {
0513: continue;
0514: }
0515: }
0516:
0517: try {
0518: pstmt.setMaxRows(crs.getMaxRows());
0519: pstmt.setMaxFieldSize(crs.getMaxFieldSize());
0520: pstmt.setEscapeProcessing(crs.getEscapeProcessing());
0521: pstmt.setQueryTimeout(crs.getQueryTimeout());
0522: } catch (Exception ex) {
0523: // Older driver don't support these operations.
0524: }
0525:
0526: ResultSet rs = null;
0527: rs = pstmt.executeQuery();
0528: if (rs.next() == true) {
0529:
0530: if (rs.next()) {
0531: /** More than one row conflict.
0532: * If rs has only one row we are able to
0533: * uniquely identify the row where update
0534: * have to happen else if more than one
0535: * row implies we cannot uniquely identify the row
0536: * where we have to do updates.
0537: * crs.setKeyColumns needs to be set to
0538: * come out of this situation.
0539: */
0540:
0541: return true;
0542: }
0543:
0544: // don't close the rs
0545: // we require the record in rs to be used.
0546: // rs.close();
0547: // pstmt.close();
0548: rs.first();
0549:
0550: // how many fields need to be updated
0551: int colsNotChanged = 0;
0552: Vector cols = new Vector();
0553: String updateExec = new String(updateCmd);
0554: Object orig;
0555: Object curr;
0556: Object rsval;
0557: boolean boolNull = true;
0558: Object objVal = null;
0559:
0560: // There's only one row and the cursor
0561: // needs to be on that row.
0562:
0563: boolean first = true;
0564: boolean flag = true;
0565:
0566: this .crsResolve.moveToInsertRow();
0567:
0568: for (i = 1; i <= callerColumnCount; i++) {
0569: orig = origVals.getObject(i);
0570: curr = crs.getObject(i);
0571: rsval = rs.getObject(i);
0572:
0573: // reset boolNull if it had been set
0574: boolNull = true;
0575:
0576: /** This addtional checking has been added when the current value
0577: * in the DB is null, but the DB had a different value when the
0578: * data was actaully fetched into the CachedRowSet.
0579: **/
0580:
0581: if (rsval == null && orig != null) {
0582: // value in db has changed
0583: // don't proceed with synchronization
0584: // get the value in db and pass it to the resolver.
0585:
0586: iChangedValsinDbOnly++;
0587: // Set the boolNull to false,
0588: // in order to set the actual value;
0589: boolNull = false;
0590: objVal = rsval;
0591: }
0592:
0593: /** Adding the checking for rsval to be "not" null or else
0594: * it would through a NullPointerException when the values
0595: * are compared.
0596: **/
0597:
0598: else if (rsval != null && (!rsval.equals(orig))) {
0599: // value in db has changed
0600: // don't proceed with synchronization
0601: // get the value in db and pass it to the resolver.
0602:
0603: iChangedValsinDbOnly++;
0604: // Set the boolNull to false,
0605: // in order to set the actual value;
0606: boolNull = false;
0607: objVal = rsval;
0608: } else if ((orig == null || curr == null)) {
0609:
0610: /** Adding the additonal condition of checking for "flag"
0611: * boolean variable, which would otherwise result in
0612: * building a invalid query, as the comma would not be
0613: * added to the query string.
0614: **/
0615:
0616: if (first == false || flag == false) {
0617: updateExec += ", ";
0618: }
0619: updateExec += crs.getMetaData()
0620: .getColumnName(i);
0621: cols.add(new Integer(i));
0622: updateExec += " = ? ";
0623: first = false;
0624:
0625: /** Adding the extra condition for orig to be "not" null as the
0626: * condition for orig to be null is take prior to this, if this
0627: * is not added it will result in a NullPointerException when
0628: * the values are compared.
0629: **/
0630:
0631: } else if (orig.equals(curr)) {
0632: colsNotChanged++;
0633: //nothing to update in this case since values are equal
0634:
0635: /** Adding the extra condition for orig to be "not" null as the
0636: * condition for orig to be null is take prior to this, if this
0637: * is not added it will result in a NullPointerException when
0638: * the values are compared.
0639: **/
0640:
0641: } else if (orig.equals(curr) == false) {
0642: // When values from db and values in CachedRowSet are not equal,
0643: // if db value is same as before updation for each col in
0644: // the row before fetching into CachedRowSet,
0645: // only then we go ahead with updation, else we
0646: // throw SyncProviderException.
0647:
0648: // if value has changed in db after fetching from db
0649: // for some cols of the row and at the same time, some other cols
0650: // have changed in CachedRowSet, no synchronization happens
0651:
0652: // Synchronization happens only when data when fetching is
0653: // same or at most has changed in cachedrowset
0654:
0655: // check orig value with what is there in crs for a column
0656: // before updation in crs.
0657:
0658: if (crs.columnUpdated(i)) {
0659: if (rsval.equals(orig)) {
0660: // At this point we are sure that
0661: // the value updated in crs was from
0662: // what is in db now and has not changed
0663: if (flag == false || first == false) {
0664: updateExec += ", ";
0665: }
0666: updateExec += crs.getMetaData()
0667: .getColumnName(i);
0668: cols.add(new Integer(i));
0669: updateExec += " = ? ";
0670: flag = false;
0671: } else {
0672: // Here the value has changed in the db after
0673: // data was fetched
0674: // Plus store this row from CachedRowSet and keep it
0675: // in a new CachedRowSet
0676: boolNull = false;
0677: objVal = rsval;
0678: iChangedValsInDbAndCRS++;
0679: }
0680: }
0681: }
0682:
0683: if (!boolNull) {
0684: this .crsResolve.updateObject(i, objVal);
0685: } else {
0686: this .crsResolve.updateNull(i);
0687: }
0688: } //end for
0689:
0690: this .crsResolve.insertRow();
0691: this .crsResolve.moveToCurrentRow();
0692:
0693: /**
0694: * if nothing has changed return now - this can happen
0695: * if column is updated to the same value.
0696: * if colsNotChanged == callerColumnCount implies we are updating
0697: * the database with ALL COLUMNS HAVING SAME VALUES,
0698: * so skip going to database, else do as usual.
0699: **/
0700: if ((first == false && cols.size() == 0)
0701: || colsNotChanged == callerColumnCount) {
0702: return false;
0703: }
0704:
0705: if (iChangedValsInDbAndCRS != 0
0706: || iChangedValsinDbOnly != 0) {
0707: return true;
0708: }
0709:
0710: updateExec += updateWhere;
0711:
0712: pstmt = con.prepareStatement(updateExec);
0713:
0714: // Comments needed here
0715: for (i = 0; i < cols.size(); i++) {
0716: Object obj = crs.getObject(((Integer) cols.get(i))
0717: .intValue());
0718: if (obj != null)
0719: pstmt.setObject(i + 1, obj);
0720: else
0721: pstmt.setNull(i + 1, crs.getMetaData()
0722: .getColumnType(i + 1));
0723: }
0724: idx = i;
0725:
0726: // Comments needed here
0727: for (i = 0; i < keyCols.length; i++) {
0728: if (params[i] != null) {
0729: pstmt.setObject(++idx, params[i]);
0730: } else {
0731: continue;
0732: }
0733: }
0734:
0735: i = pstmt.executeUpdate();
0736:
0737: /**
0738: * i should be equal to 1(row count), because we update
0739: * one row(returned as row count) at a time, if all goes well.
0740: * if 1 != 1, this implies we have not been able to
0741: * do updations properly i.e there is a conflict in database
0742: * versus what is in CachedRowSet for this particular row.
0743: **/
0744:
0745: return false;
0746:
0747: } else {
0748: /**
0749: * Cursor will be here, if the ResultSet may not return even a single row
0750: * i.e. we can't find the row where to update because it has been deleted
0751: * etc. from the db.
0752: * Present the whole row as null to user, to force null to be sync'ed
0753: * and hence nothing to be synced.
0754: *
0755: * NOTE:
0756: * ------
0757: * In the database if a column that is mapped to java.sql.Types.REAL stores
0758: * a Double value and is compared with value got from ResultSet.getFloat()
0759: * no row is retrieved and will throw a SyncProviderException. For details
0760: * see bug Id 5053830
0761: **/
0762: return true;
0763: }
0764: } catch (SQLException ex) {
0765: ex.printStackTrace();
0766: // if executeUpdate fails it will come here,
0767: // update crsResolve with null rows
0768: this .crsResolve.moveToInsertRow();
0769:
0770: for (i = 1; i <= callerColumnCount; i++) {
0771: this .crsResolve.updateNull(i);
0772: }
0773:
0774: this .crsResolve.insertRow();
0775: this .crsResolve.moveToCurrentRow();
0776:
0777: return true;
0778: }
0779: }
0780:
0781: /**
0782: * Inserts a row that has been inserted into the given
0783: * <code>CachedRowSet</code> object into the data source from which
0784: * the rowset is derived, returning <code>false</code> if the insertion
0785: * was successful.
0786: *
0787: * @param crs the <code>CachedRowSet</code> object that has had a row inserted
0788: * and to whose underlying data source the row will be inserted
0789: * @param pstmt the <code>PreparedStatement</code> object that will be used
0790: * to execute the insertion
0791: * @return <code>false</code> to indicate that the insertion was successful;
0792: * <code>true</code> otherwise
0793: * @throws SQLException if a database access error occurs
0794: */
0795: private boolean insertNewRow(CachedRowSet crs,
0796: PreparedStatement pstmt, CachedRowSetImpl crsRes)
0797: throws SQLException {
0798: int i = 0;
0799: int icolCount = crs.getMetaData().getColumnCount();
0800:
0801: boolean returnVal = false;
0802: PreparedStatement pstmtSel = con.prepareStatement(selectCmd,
0803: ResultSet.TYPE_SCROLL_SENSITIVE,
0804: ResultSet.CONCUR_READ_ONLY);
0805: ResultSet rs, rs2 = null;
0806: DatabaseMetaData dbmd = con.getMetaData();
0807: rs = pstmtSel.executeQuery();
0808: String table = crs.getTableName();
0809: rs2 = dbmd.getPrimaryKeys(null, null, table);
0810: String[] primaryKeys = new String[icolCount];
0811: int k = 0;
0812: while (rs2.next()) {
0813: String pkcolname = rs2.getString("COLUMN_NAME");
0814: primaryKeys[k] = pkcolname;
0815: k++;
0816: }
0817:
0818: if (rs.next()) {
0819: for (int j = 0; j < primaryKeys.length; j++) {
0820: if (primaryKeys[j] != null) {
0821: if (crs.getObject(primaryKeys[j]) == null) {
0822: break;
0823: }
0824: String crsPK = (crs.getObject(primaryKeys[j]))
0825: .toString();
0826: String rsPK = (rs.getObject(primaryKeys[j]))
0827: .toString();
0828: if (crsPK.equals(rsPK)) {
0829: returnVal = true;
0830: this .crsResolve.moveToInsertRow();
0831: for (i = 1; i <= icolCount; i++) {
0832: String colname = (rs.getMetaData())
0833: .getColumnName(i);
0834: if (colname.equals(primaryKeys[j]))
0835: this .crsResolve.updateObject(i, rsPK);
0836: else
0837: this .crsResolve.updateNull(i);
0838: }
0839: this .crsResolve.insertRow();
0840: this .crsResolve.moveToCurrentRow();
0841: }
0842: }
0843: }
0844: }
0845: if (returnVal)
0846: return returnVal;
0847:
0848: try {
0849: for (i = 1; i <= icolCount; i++) {
0850: Object obj = crs.getObject(i);
0851: if (obj != null) {
0852: pstmt.setObject(i, obj);
0853: } else {
0854: pstmt
0855: .setNull(i, crs.getMetaData()
0856: .getColumnType(i));
0857: }
0858: }
0859:
0860: i = pstmt.executeUpdate();
0861: return false;
0862:
0863: } catch (SQLException ex) {
0864: /**
0865: * Cursor will come here if executeUpdate fails.
0866: * There can be many reasons why the insertion failed,
0867: * one can be violation of primary key.
0868: * Hence we cannot exactly identify why the insertion failed
0869: * Present the current row as a null row to the user.
0870: **/
0871: this .crsResolve.moveToInsertRow();
0872:
0873: for (i = 1; i <= icolCount; i++) {
0874: this .crsResolve.updateNull(i);
0875: }
0876:
0877: this .crsResolve.insertRow();
0878: this .crsResolve.moveToCurrentRow();
0879:
0880: return true;
0881: }
0882: }
0883:
0884: /**
0885: * Deletes the row in the underlying data source that corresponds to
0886: * a row that has been deleted in the given <code> CachedRowSet</code> object
0887: * and returns <code>false</code> if the deletion was successful.
0888: * <P>
0889: * This method is called internally by this writer's <code>writeData</code>
0890: * method when a row in the rowset has been deleted. The values in the
0891: * deleted row are the same as those that are stored in the original row
0892: * of the given <code>CachedRowSet</code> object. If the values in the
0893: * original row differ from the row in the underlying data source, the row
0894: * in the data source is not deleted, and <code>deleteOriginalRow</code>
0895: * returns <code>true</code> to indicate that there was a conflict.
0896: *
0897: *
0898: * @return <code>false</code> if the deletion was successful, which means that
0899: * there was no conflict; <code>true</code> otherwise
0900: * @throws SQLException if there was a database access error
0901: */
0902: private boolean deleteOriginalRow(CachedRowSet crs,
0903: CachedRowSetImpl crsRes) throws SQLException {
0904: PreparedStatement pstmt;
0905: int i;
0906: int idx = 0;
0907: String strSelect;
0908: // Select the row from the database.
0909: ResultSet origVals = crs.getOriginalRow();
0910: origVals.next();
0911:
0912: deleteWhere = buildWhereClause(deleteWhere, origVals);
0913: pstmt = con.prepareStatement(selectCmd + deleteWhere,
0914: ResultSet.TYPE_SCROLL_SENSITIVE,
0915: ResultSet.CONCUR_READ_ONLY);
0916:
0917: for (i = 0; i < keyCols.length; i++) {
0918: if (params[i] != null) {
0919: pstmt.setObject(++idx, params[i]);
0920: } else {
0921: continue;
0922: }
0923: }
0924:
0925: try {
0926: pstmt.setMaxRows(crs.getMaxRows());
0927: pstmt.setMaxFieldSize(crs.getMaxFieldSize());
0928: pstmt.setEscapeProcessing(crs.getEscapeProcessing());
0929: pstmt.setQueryTimeout(crs.getQueryTimeout());
0930: } catch (Exception ex) {
0931: /*
0932: * Older driver don't support these operations...
0933: */
0934: ;
0935: }
0936:
0937: ResultSet rs = pstmt.executeQuery();
0938:
0939: if (rs.next() == true) {
0940: if (rs.next()) {
0941: // more than one row
0942: return true;
0943: }
0944: rs.first();
0945:
0946: // Now check all the values in rs to be same in
0947: // db also before actually going ahead with deleting
0948: boolean boolChanged = false;
0949:
0950: crsRes.moveToInsertRow();
0951:
0952: for (i = 1; i <= crs.getMetaData().getColumnCount(); i++) {
0953:
0954: Object original = origVals.getObject(i);
0955: Object changed = rs.getObject(i);
0956:
0957: if (original != null && changed != null) {
0958: if (!(original.toString()).equals(changed
0959: .toString())) {
0960: boolChanged = true;
0961: crsRes.updateObject(i, origVals.getObject(i));
0962: }
0963: } else {
0964: crsRes.updateNull(i);
0965: }
0966: }
0967:
0968: crsRes.insertRow();
0969: crsRes.moveToCurrentRow();
0970:
0971: if (boolChanged) {
0972: // do not delete as values in db have changed
0973: // deletion will not happen for this row from db
0974: // exit now returning true. i.e. conflict
0975: return true;
0976: } else {
0977: // delete the row.
0978: // Go ahead with deleting,
0979: // don't do anything here
0980: }
0981:
0982: String cmd = deleteCmd + deleteWhere;
0983: pstmt = con.prepareStatement(cmd);
0984:
0985: idx = 0;
0986: for (i = 0; i < keyCols.length; i++) {
0987: if (params[i] != null) {
0988: pstmt.setObject(++idx, params[i]);
0989: } else {
0990: continue;
0991: }
0992: }
0993:
0994: if (pstmt.executeUpdate() != 1) {
0995: return true;
0996: }
0997: pstmt.close();
0998: } else {
0999: // didn't find the row
1000: return true;
1001: }
1002:
1003: // no conflict
1004: return false;
1005: }
1006:
1007: /**
1008: * Sets the reader for this writer to the given reader.
1009: *
1010: * @throws SQLException if a database access error occurs
1011: */
1012: public void setReader(CachedRowSetReader reader)
1013: throws SQLException {
1014: this .reader = reader;
1015: }
1016:
1017: /**
1018: * Gets the reader for this writer.
1019: *
1020: * @throws SQLException if a database access error occurs
1021: */
1022: public CachedRowSetReader getReader() throws SQLException {
1023: return reader;
1024: }
1025:
1026: /**
1027: * Composes a <code>SELECT</code>, <code>UPDATE</code>, <code>INSERT</code>,
1028: * and <code>DELETE</code> statement that can be used by this writer to
1029: * write data to the data source backing the given <code>CachedRowSet</code>
1030: * object.
1031: *
1032: * @ param caller a <code>CachedRowSet</code> object for which this
1033: * <code>CachedRowSetWriter</code> object is the writer
1034: * @throws SQLException if a database access error occurs
1035: */
1036: private void initSQLStatements(CachedRowSet caller)
1037: throws SQLException {
1038:
1039: int i;
1040:
1041: callerMd = caller.getMetaData();
1042: callerColumnCount = callerMd.getColumnCount();
1043: if (callerColumnCount < 1)
1044: // No data, so return.
1045: return;
1046:
1047: /*
1048: * If the RowSet has a Table name we should use it.
1049: * This is really a hack to get round the fact that
1050: * a lot of the jdbc drivers can't provide the tab.
1051: */
1052: String table = caller.getTableName();
1053: if (table == null) {
1054: /*
1055: * attempt to build a table name using the info
1056: * that the driver gave us for the first column
1057: * in the source result set.
1058: */
1059: table = callerMd.getTableName(1);
1060: if (table == null || table.length() == 0) {
1061: throw new SQLException(resBundle.handleGetObject(
1062: "crswriter.tname").toString());
1063: }
1064: }
1065: String catalog = callerMd.getCatalogName(1);
1066: String schema = callerMd.getSchemaName(1);
1067: DatabaseMetaData dbmd = con.getMetaData();
1068:
1069: /*
1070: * Compose a SELECT statement. There are three parts.
1071: */
1072:
1073: // Project List
1074: selectCmd = "SELECT ";
1075: for (i = 1; i <= callerColumnCount; i++) {
1076: selectCmd += callerMd.getColumnName(i);
1077: if (i < callerMd.getColumnCount())
1078: selectCmd += ", ";
1079: else
1080: selectCmd += " ";
1081: }
1082:
1083: // FROM clause.
1084: selectCmd += "FROM "
1085: + buildTableName(dbmd, catalog, schema, table);
1086:
1087: /*
1088: * Compose an UPDATE statement.
1089: */
1090: updateCmd = "UPDATE "
1091: + buildTableName(dbmd, catalog, schema, table);
1092:
1093: /**
1094: * The following block of code is for checking a particular type of
1095: * query where in there is a where clause. Without this block, if a
1096: * SQL statement is built the "where" clause will appear twice hence
1097: * the DB errors out and a SQLException is thrown. This code also
1098: * considers that the where clause is in the right place as the
1099: * CachedRowSet object would already have been populated with this
1100: * query before coming to this point.
1101: **/
1102:
1103: String tempupdCmd = updateCmd.toLowerCase();
1104:
1105: int idxupWhere = tempupdCmd.indexOf("where");
1106:
1107: if (idxupWhere != -1) {
1108: updateCmd = updateCmd.substring(0, idxupWhere);
1109: }
1110: updateCmd += "SET ";
1111:
1112: /*
1113: * Compose an INSERT statement.
1114: */
1115: insertCmd = "INSERT INTO "
1116: + buildTableName(dbmd, catalog, schema, table);
1117: // Column list
1118: insertCmd += "(";
1119: for (i = 1; i <= callerColumnCount; i++) {
1120: insertCmd += callerMd.getColumnName(i);
1121: if (i < callerMd.getColumnCount())
1122: insertCmd += ", ";
1123: else
1124: insertCmd += ") VALUES (";
1125: }
1126: for (i = 1; i <= callerColumnCount; i++) {
1127: insertCmd += "?";
1128: if (i < callerColumnCount)
1129: insertCmd += ", ";
1130: else
1131: insertCmd += ")";
1132: }
1133:
1134: /*
1135: * Compose a DELETE statement.
1136: */
1137: deleteCmd = "DELETE FROM "
1138: + buildTableName(dbmd, catalog, schema, table);
1139:
1140: /*
1141: * set the key desriptors that will be
1142: * needed to construct where clauses.
1143: */
1144: buildKeyDesc(caller);
1145: }
1146:
1147: /**
1148: * Returns a fully qualified table name built from the given catalog and
1149: * table names. The given metadata object is used to get the proper order
1150: * and separator.
1151: *
1152: * @param dbmd a <code>DatabaseMetaData</code> object that contains metadata
1153: * about this writer's <code>CachedRowSet</code> object
1154: * @param catalog a <code>String</code> object with the rowset's catalog
1155: * name
1156: * @param table a <code>String</code> object with the name of the table from
1157: * which this writer's rowset was derived
1158: * @return a <code>String</code> object with the fully qualified name of the
1159: * table from which this writer's rowset was derived
1160: * @throws SQLException if a database access error occurs
1161: */
1162: private String buildTableName(DatabaseMetaData dbmd,
1163: String catalog, String schema, String table)
1164: throws SQLException {
1165:
1166: // trim all the leading and trailing whitespaces,
1167: // white spaces can never be catalog, schema or a table name.
1168:
1169: String cmd = new String();
1170:
1171: catalog = catalog.trim();
1172: schema = schema.trim();
1173: table = table.trim();
1174:
1175: if (dbmd.isCatalogAtStart() == true) {
1176: if (catalog != null && catalog.length() > 0) {
1177: cmd += catalog + dbmd.getCatalogSeparator();
1178: }
1179: if (schema != null && schema.length() > 0) {
1180: cmd += schema + ".";
1181: }
1182: cmd += table;
1183: } else {
1184: if (schema != null && schema.length() > 0) {
1185: cmd += schema + ".";
1186: }
1187: cmd += table;
1188: if (catalog != null && catalog.length() > 0) {
1189: cmd += dbmd.getCatalogSeparator() + catalog;
1190: }
1191: }
1192: cmd += " ";
1193: return cmd;
1194: }
1195:
1196: /**
1197: * Assigns to the given <code>CachedRowSet</code> object's
1198: * <code>params</code>
1199: * field an array whose length equals the number of columns needed
1200: * to uniquely identify a row in the rowset. The array is given
1201: * values by the method <code>buildWhereClause</code>.
1202: * <P>
1203: * If the <code>CachedRowSet</code> object's <code>keyCols</code>
1204: * field has length <code>0</code> or is <code>null</code>, the array
1205: * is set with the column number of every column in the rowset.
1206: * Otherwise, the array in the field <code>keyCols</code> is set with only
1207: * the column numbers of the columns that are required to form a unique
1208: * identifier for a row.
1209: *
1210: * @param crs the <code>CachedRowSet</code> object for which this
1211: * <code>CachedRowSetWriter</code> object is the writer
1212: *
1213: * @throws SQLException if a database access error occurs
1214: */
1215: private void buildKeyDesc(CachedRowSet crs) throws SQLException {
1216:
1217: keyCols = crs.getKeyColumns();
1218: if (keyCols == null || keyCols.length == 0) {
1219: keyCols = new int[callerColumnCount];
1220: for (int i = 0; i < keyCols.length;) {
1221: keyCols[i] = ++i;
1222: }
1223: }
1224: params = new Object[keyCols.length];
1225: }
1226:
1227: /**
1228: * Constructs an SQL <code>WHERE</code> clause using the given
1229: * string as a starting point. The resulting clause will contain
1230: * a column name and " = ?" for each key column, that is, each column
1231: * that is needed to form a unique identifier for a row in the rowset.
1232: * This <code>WHERE</code> clause can be added to
1233: * a <code>PreparedStatement</code> object that updates, inserts, or
1234: * deletes a row.
1235: * <P>
1236: * This method uses the given result set to access values in the
1237: * <code>CachedRowSet</code> object that called this writer. These
1238: * values are used to build the array of parameters that will serve as
1239: * replacements for the "?" parameter placeholders in the
1240: * <code>PreparedStatement</code> object that is sent to the
1241: * <code>CachedRowSet</code> object's underlying data source.
1242: *
1243: * @param whereClause a <code>String</code> object that is an empty
1244: * string ("")
1245: * @param rs a <code>ResultSet</code> object that can be used
1246: * to access the <code>CachedRowSet</code> object's data
1247: * @return a <code>WHERE</code> clause of the form "<code>WHERE</code>
1248: * columnName = ? AND columnName = ? AND columnName = ? ..."
1249: * @throws SQLException if a database access error occurs
1250: */
1251: private String buildWhereClause(String whereClause, ResultSet rs)
1252: throws SQLException {
1253: whereClause = "WHERE ";
1254:
1255: for (int i = 0; i < keyCols.length; i++) {
1256: if (i > 0) {
1257: whereClause += "AND ";
1258: }
1259: whereClause += callerMd.getColumnName(keyCols[i]);
1260: params[i] = rs.getObject(keyCols[i]);
1261: if (rs.wasNull() == true) {
1262: whereClause += " IS NULL ";
1263: } else {
1264: whereClause += " = ? ";
1265: }
1266: }
1267: return whereClause;
1268: }
1269:
1270: void updateResolvedConflictToDB(CachedRowSet crs, Connection con)
1271: throws SQLException {
1272: //String updateExe = ;
1273: PreparedStatement pStmt;
1274: String strWhere = "WHERE ";
1275: String strExec = " ";
1276: String strUpdate = "UPDATE ";
1277: int icolCount = crs.getMetaData().getColumnCount();
1278: int keyColumns[] = crs.getKeyColumns();
1279: Object param[];
1280: String strSet = "";
1281:
1282: strWhere = buildWhereClause(strWhere, crs);
1283:
1284: if (keyColumns == null || keyColumns.length == 0) {
1285: keyColumns = new int[icolCount];
1286: for (int i = 0; i < keyColumns.length;) {
1287: keyColumns[i] = ++i;
1288: }
1289: }
1290: param = new Object[keyColumns.length];
1291:
1292: strUpdate = "UPDATE "
1293: + buildTableName(con.getMetaData(), crs.getMetaData()
1294: .getCatalogName(1), crs.getMetaData()
1295: .getSchemaName(1), crs.getTableName());
1296:
1297: // changed or updated values will become part of
1298: // set clause here
1299: strUpdate += "SET ";
1300:
1301: boolean first = true;
1302:
1303: for (int i = 1; i <= icolCount; i++) {
1304: if (crs.columnUpdated(i)) {
1305: if (first == false) {
1306: strSet += ", ";
1307: }
1308: strSet += crs.getMetaData().getColumnName(i);
1309: strSet += " = ? ";
1310: first = false;
1311: } //end if
1312: } //end for
1313:
1314: // keycols will become part of where clause
1315: strUpdate += strSet;
1316: strWhere = "WHERE ";
1317:
1318: for (int i = 0; i < keyColumns.length; i++) {
1319: if (i > 0) {
1320: strWhere += "AND ";
1321: }
1322: strWhere += crs.getMetaData().getColumnName(keyColumns[i]);
1323: param[i] = crs.getObject(keyColumns[i]);
1324: if (crs.wasNull() == true) {
1325: strWhere += " IS NULL ";
1326: } else {
1327: strWhere += " = ? ";
1328: }
1329: }
1330: strUpdate += strWhere;
1331:
1332: pStmt = con.prepareStatement(strUpdate);
1333:
1334: int idx = 0;
1335: for (int i = 0; i < icolCount; i++) {
1336: if (crs.columnUpdated(i + 1)) {
1337: Object obj = crs.getObject(i + 1);
1338: if (obj != null) {
1339: pStmt.setObject(++idx, obj);
1340: } else {
1341: pStmt.setNull(i + 1, crs.getMetaData()
1342: .getColumnType(i + 1));
1343: } //end if ..else
1344: } //end if crs.column...
1345: } //end for
1346:
1347: // Set the key cols for after WHERE =? clause
1348: for (int i = 0; i < keyColumns.length; i++) {
1349: if (param[i] != null) {
1350: pStmt.setObject(++idx, param[i]);
1351: }
1352: }
1353:
1354: int id = pStmt.executeUpdate();
1355: }
1356:
1357: /**
1358: *
1359: */
1360: public void commit() throws SQLException {
1361: con.commit();
1362: if (reader.getCloseConnection() == true) {
1363: con.close();
1364: }
1365: }
1366:
1367: public void commit(CachedRowSetImpl crs, boolean updateRowset)
1368: throws SQLException {
1369: con.commit();
1370: if (updateRowset) {
1371: if (crs.getCommand() != null)
1372: crs.execute(con);
1373: }
1374:
1375: if (reader.getCloseConnection() == true) {
1376: con.close();
1377: }
1378: }
1379:
1380: /**
1381: *
1382: */
1383: public void rollback() throws SQLException {
1384: con.rollback();
1385: if (reader.getCloseConnection() == true) {
1386: con.close();
1387: }
1388: }
1389:
1390: /**
1391: *
1392: */
1393: public void rollback(Savepoint s) throws SQLException {
1394: con.rollback(s);
1395: if (reader.getCloseConnection() == true) {
1396: con.close();
1397: }
1398: }
1399:
1400: }
|