1440 lines · 53.4 KB · Java
RawDownload
/* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.rowset.internal; import java.sql.*;import javax.sql.*;import java.util.*;import java.io.*; import com.sun.rowset.*;import java.text.MessageFormat;import javax.sql.rowset.*;import javax.sql.rowset.serial.SQLInputImpl;import javax.sql.rowset.serial.SerialArray;import javax.sql.rowset.serial.SerialBlob;import javax.sql.rowset.serial.SerialClob;import javax.sql.rowset.serial.SerialStruct;import javax.sql.rowset.spi.*;  /** * The facility called on internally by the <code>RIOptimisticProvider</code> implementation to * propagate changes back to the data source from which the rowset got its data. * <P> * A <code>CachedRowSetWriter</code> object, called a writer, has the public * method <code>writeData</code> for writing modified data to the underlying data source. * This method is invoked by the rowset internally and is never invoked directly by an application. * A writer also has public methods for setting and getting * the <code>CachedRowSetReader</code> object, called a reader, that is associated * with the writer. The remainder of the methods in this class are private and * are invoked internally, either directly or indirectly, by the method * <code>writeData</code>. * <P> * Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and * the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects. * Standard JDBC RowSet implementations provide an object instance of this * writer by invoking the <code>SyncProvider.getRowSetWriter()</code> method. * * @version 0.2 * @author Jonathan Bruce * @see javax.sql.rowset.spi.SyncProvider * @see javax.sql.rowset.spi.SyncFactory * @see javax.sql.rowset.spi.SyncFactoryException */public class CachedRowSetWriter implements TransactionalWriter, Serializable { /** * The <code>Connection</code> object that this writer will use to make a * connection to the data source to which it will write data. * */    private transient Connection con; /** * The SQL <code>SELECT</code> command that this writer will call * internally. The method <code>initSQLStatements</code> builds this * command by supplying the words "SELECT" and "FROM," and using * metadata to get the table name and column names . * * @serial */    private String selectCmd; /** * The SQL <code>UPDATE</code> command that this writer will call * internally to write data to the rowset's underlying data source. * The method <code>initSQLStatements</code> builds this <code>String</code> * object. * * @serial */    private String updateCmd; /** * The SQL <code>WHERE</code> clause the writer will use for update * statements in the <code>PreparedStatement</code> object * it sends to the underlying data source. * * @serial */    private String updateWhere; /** * The SQL <code>DELETE</code> command that this writer will call * internally to delete a row in the rowset's underlying data source. * * @serial */    private String deleteCmd; /** * The SQL <code>WHERE</code> clause the writer will use for delete * statements in the <code>PreparedStatement</code> object * it sends to the underlying data source. * * @serial */    private String deleteWhere; /** * The SQL <code>INSERT INTO</code> command that this writer will internally use * to insert data into the rowset's underlying data source.  The method * <code>initSQLStatements</code> builds this command with a question * mark parameter placeholder for each column in the rowset. * * @serial */    private String insertCmd; /** * An array containing the column numbers of the columns that are * needed to uniquely identify a row in the <code>CachedRowSet</code> object * for which this <code>CachedRowSetWriter</code> object is the writer. * * @serial */    private int[] keyCols; /** * An array of the parameters that should be used to set the parameter * placeholders in a <code>PreparedStatement</code> object that this * writer will execute. * * @serial */    private Object[] params; /** * The <code>CachedRowSetReader</code> object that has been * set as the reader for the <code>CachedRowSet</code> object * for which this <code>CachedRowSetWriter</code> object is the writer. * * @serial */    private CachedRowSetReader reader; /** * The <code>ResultSetMetaData</code> object that contains information * about the columns in the <code>CachedRowSet</code> object * for which this <code>CachedRowSetWriter</code> object is the writer. * * @serial */    private ResultSetMetaData callerMd; /** * The number of columns in the <code>CachedRowSet</code> object * for which this <code>CachedRowSetWriter</code> object is the writer. * * @serial */    private int callerColumnCount; /** * This <code>CachedRowSet<code> will hold the conflicting values *  retrieved from the db and hold it. */    private CachedRowSetImpl crsResolve; /** * This <code>ArrayList<code> will hold the values of SyncResolver.* */    private ArrayList status; /** * This will check whether the same field value has changed both * in database and CachedRowSet. */    private int iChangedValsInDbAndCRS; /** * This will hold the number of cols for which the values have * changed only in database. */    private int iChangedValsinDbOnly ;     private JdbcRowSetResourceBundle resBundle;     public CachedRowSetWriter() {       try {               resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();       } catch(IOException ioe) {               throw new RuntimeException(ioe);       }    } /** * Propagates changes in the given <code>RowSet</code> object * back to its underlying data source and returns <code>true</code> * if successful. The writer will check to see if * the data in the pre-modified rowset (the original values) differ * from the data in the underlying data source.  If data in the data * source has been modified by someone else, there is a conflict, * and in that case, the writer will not write to the data source. * In other words, the writer uses an optimistic concurrency algorithm: * It checks for conflicts before making changes rather than restricting * access for concurrent users. * <P> * This method is called by the rowset internally when * the application invokes the method <code>acceptChanges</code>. * The <code>writeData</code> method in turn calls private methods that * it defines internally. * The following is a general summary of what the method * <code>writeData</code> does, much of which is accomplished * through calls to its own internal methods. * <OL> * <LI>Creates a <code>CachedRowSet</code> object from the given *     <code>RowSet</code> object * <LI>Makes a connection with the data source *   <UL> *      <LI>Disables autocommit mode if it is not already disabled *      <LI>Sets the transaction isolation level to that of the rowset *   </UL> * <LI>Checks to see if the reader has read new data since the writer *     was last called and, if so, calls the method *    <code>initSQLStatements</code> to initialize new SQL statements *   <UL> *       <LI>Builds new <code>SELECT</code>, <code>UPDATE</code>, *           <code>INSERT</code>, and <code>DELETE</code> statements *       <LI>Uses the <code>CachedRowSet</code> object's metadata to *           determine the table name, column names, and the columns *           that make up the primary key *   </UL> * <LI>When there is no conflict, propagates changes made to the *     <code>CachedRowSet</code> object back to its underlying data source *   <UL> *      <LI>Iterates through each row of the <code>CachedRowSet</code> object *          to determine whether it has been updated, inserted, or deleted *      <LI>If the corresponding row in the data source has not been changed *          since the rowset last read its *          values, the writer will use the appropriate command to update, *          insert, or delete the row *      <LI>If any data in the data source does not match the original values *          for the <code>CachedRowSet</code> object, the writer will roll *          back any changes it has made to the row in the data source. *   </UL> * </OL> * * @return <code>true</code> if changes to the rowset were successfully *         written to the rowset's underlying data source; *         <code>false</code> otherwise */    public boolean writeData(RowSetInternal caller) throws SQLException {        boolean conflict = false;        boolean showDel = false;        PreparedStatement pstmtIns = null;        iChangedValsInDbAndCRS = 0;        iChangedValsinDbOnly = 0;         // We assume caller is a CachedRowSet        CachedRowSetImpl crs = (CachedRowSetImpl)caller;        // crsResolve = new CachedRowSetImpl();        this.crsResolve = new CachedRowSetImpl();;         // The reader is registered with the writer at design time.        // This is not required, in general.  The reader has logic        // to get a JDBC connection, so call it.         con = reader.connect(caller);          if (con == null) {            throw new SQLException(resBundle.handleGetObject("crswriter.connect").toString());        }         /*         // Fix 6200646.         // Don't change the connection or transaction properties. This will fail in a         // J2EE container.        if (con.getAutoCommit() == true)  {            con.setAutoCommit(false);        }         con.setTransactionIsolation(crs.getTransactionIsolation());        */         initSQLStatements(crs);        int iColCount;         RowSetMetaDataImpl rsmdWrite = (RowSetMetaDataImpl)crs.getMetaData();        RowSetMetaDataImpl rsmdResolv = new RowSetMetaDataImpl();         iColCount = rsmdWrite.getColumnCount();        int sz= crs.size()+1;        status = new ArrayList(sz);         status.add(0,null);        rsmdResolv.setColumnCount(iColCount);         for(int i =1; i <= iColCount; i++) {            rsmdResolv.setColumnType(i, rsmdWrite.getColumnType(i));            rsmdResolv.setColumnName(i, rsmdWrite.getColumnName(i));            rsmdResolv.setNullable(i, ResultSetMetaData.columnNullableUnknown);        }        this.crsResolve.setMetaData(rsmdResolv);         // moved outside the insert inner loop        //pstmtIns = con.prepareStatement(insertCmd);         if (callerColumnCount < 1) {            // No data, so return success.            if (reader.getCloseConnection() == true)                    con.close();            return true;        }        // We need to see rows marked for deletion.        showDel = crs.getShowDeleted();        crs.setShowDeleted(true);         // Look at all the rows.        crs.beforeFirst();         int rows =1;        while (crs.next()) {            if (crs.rowDeleted()) {                // The row has been deleted.                if (conflict = (deleteOriginalRow(crs, this.crsResolve)) == true) {                       status.add(rows, Integer.valueOf(SyncResolver.DELETE_ROW_CONFLICT));                } else {                      // delete happened without any occurrence of conflicts                      // so update status accordingly                       status.add(rows, Integer.valueOf(SyncResolver.NO_ROW_CONFLICT));                }            } else if (crs.rowInserted()) {                // The row has been inserted.                 pstmtIns = con.prepareStatement(insertCmd);                if ( (conflict = insertNewRow(crs, pstmtIns, this.crsResolve)) == true) {                          status.add(rows, Integer.valueOf(SyncResolver.INSERT_ROW_CONFLICT));                } else {                      // insert happened without any occurrence of conflicts                      // so update status accordingly                       status.add(rows, Integer.valueOf(SyncResolver.NO_ROW_CONFLICT));                }            } else  if (crs.rowUpdated()) {                  // The row has been updated.                       if ( conflict = (updateOriginalRow(crs)) == true) {                             status.add(rows, Integer.valueOf(SyncResolver.UPDATE_ROW_CONFLICT));               } else {                      // update happened without any occurrence of conflicts                      // so update status accordingly                      status.add(rows, Integer.valueOf(SyncResolver.NO_ROW_CONFLICT));               }             } else {               /** The row is neither of inserted, updated or deleted.                *  So set nulls in the this.crsResolve for this row,                *  as nothing is to be done for such rows.                *  Also note that if such a row has been changed in database                *  and we have not changed(inserted, updated or deleted)                *  that is fine.                **/                int icolCount = crs.getMetaData().getColumnCount();                status.add(rows, Integer.valueOf(SyncResolver.NO_ROW_CONFLICT));                 this.crsResolve.moveToInsertRow();                for(int cols=0;cols<iColCount;cols++) {                   this.crsResolve.updateNull(cols+1);                } //end for                 this.crsResolve.insertRow();                this.crsResolve.moveToCurrentRow();                 } //end if         rows++;      } //end while         // close the insert statement        if(pstmtIns!=null)        pstmtIns.close();        // reset        crs.setShowDeleted(showDel);       boolean boolConf = false;      for (int j=1;j<status.size();j++){          // ignore status for index = 0 which is set to null          if(! ((status.get(j)).equals(Integer.valueOf(SyncResolver.NO_ROW_CONFLICT)))) {              // there is at least one conflict which needs to be resolved              boolConf = true;             break;          }      }         crs.beforeFirst();        this.crsResolve.beforeFirst();     if(boolConf) {        SyncProviderException spe = new SyncProviderException(status.size() - 1+resBundle.handleGetObject("crswriter.conflictsno").toString());        //SyncResolver syncRes = spe.getSyncResolver();          SyncResolverImpl syncResImpl = (SyncResolverImpl) spe.getSyncResolver();          syncResImpl.setCachedRowSet(crs);         syncResImpl.setCachedRowSetResolver(this.crsResolve);          syncResImpl.setStatus(status);         syncResImpl.setCachedRowSetWriter(this);         throw spe;    } else {         return true;    }       /*       if (conflict == true) {            con.rollback();            return false;        } else {            con.commit();                if (reader.getCloseConnection() == true) {                       con.close();                }            return true;        }        */   } //end writeData /** * Updates the given <code>CachedRowSet</code> object's underlying data * source so that updates to the rowset are reflected in the original * data source, and returns <code>false</code> if the update was successful. * A return value of <code>true</code> indicates that there is a conflict, * meaning that a value updated in the rowset has already been changed by * someone else in the underlying data source.  A conflict can also exist * if, for example, more than one row in the data source would be affected * by the update or if no rows would be affected.  In any case, if there is * a conflict, this method does not update the underlying data source. * <P> * This method is called internally by the method <code>writeData</code> * if a row in the <code>CachedRowSet</code> object for which this * <code>CachedRowSetWriter</code> object is the writer has been updated. * * @return <code>false</code> if the update to the underlying data source is *         successful; <code>true</code> otherwise * @throws SQLException if a database access error occurs */    private boolean updateOriginalRow(CachedRowSet crs)        throws SQLException {        PreparedStatement pstmt;        int i = 0;        int idx = 0;         // Select the row from the database.        ResultSet origVals = crs.getOriginalRow();        origVals.next();         try {            updateWhere = buildWhereClause(updateWhere, origVals);               /**              *  The following block of code is for checking a particular type of              *  query where in there is a where clause. Without this block, if a              *  SQL statement is built the "where" clause will appear twice hence              *  the DB errors out and a SQLException is thrown. This code also              *  considers that the where clause is in the right place as the              *  CachedRowSet object would already have been populated with this              *  query before coming to this point.              **/              String tempselectCmd = selectCmd.toLowerCase();             int idxWhere = tempselectCmd.indexOf("where");             if(idxWhere != -1)            {               String tempSelect = selectCmd.substring(0,idxWhere);               selectCmd = tempSelect;            }             pstmt = con.prepareStatement(selectCmd + updateWhere,                        ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);             for (i = 0; i < keyCols.length; i++) {                if (params[i] != null) {

Lines 1–500 of 1440.