/* | 
|
 * Copyright (c) 2003, 2013, 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 sun.reflect.misc.ReflectUtil;  | 
|
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<Integer> 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 {  | 
|
long conflicts = 0;  | 
|
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 (deleteOriginalRow(crs, this.crsResolve)) {  | 
|
status.add(rows, SyncResolver.DELETE_ROW_CONFLICT);  | 
|
conflicts++;  | 
|
                } else { | 
|
// delete happened without any occurrence of conflicts  | 
|
                      // so update status accordingly | 
|
status.add(rows, SyncResolver.NO_ROW_CONFLICT);  | 
|
}  | 
|
} else if (crs.rowInserted()) {  | 
|
// The row has been inserted.  | 
|
pstmtIns = con.prepareStatement(insertCmd);  | 
|
if (insertNewRow(crs, pstmtIns, this.crsResolve)) {  | 
|
status.add(rows, SyncResolver.INSERT_ROW_CONFLICT);  | 
|
conflicts++;  | 
|
                } else { | 
|
// insert happened without any occurrence of conflicts  | 
|
                      // so update status accordingly | 
|
status.add(rows, SyncResolver.NO_ROW_CONFLICT);  | 
|
}  | 
|
} else if (crs.rowUpdated()) {  | 
|
                  // The row has been updated. | 
|
if (updateOriginalRow(crs)) {  | 
|
status.add(rows, SyncResolver.UPDATE_ROW_CONFLICT);  | 
|
conflicts++;  | 
|
               } else { | 
|
// update happened without any occurrence of conflicts  | 
|
                      // so update status accordingly | 
|
status.add(rows, 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, 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);  | 
|
crs.beforeFirst();  | 
|
this.crsResolve.beforeFirst();  | 
|
if(conflicts != 0) {  | 
|
SyncProviderException spe = new SyncProviderException(conflicts + " " +  | 
|
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) {  | 
|
pstmt.setObject(++idx, params[i]);  | 
|
                } else { | 
|
continue;  | 
|
}  | 
|
}  | 
|
            try { | 
|
pstmt.setMaxRows(crs.getMaxRows());  | 
|
pstmt.setMaxFieldSize(crs.getMaxFieldSize());  | 
|
pstmt.setEscapeProcessing(crs.getEscapeProcessing());  | 
|
pstmt.setQueryTimeout(crs.getQueryTimeout());  | 
|
} catch (Exception ex) {  | 
|
// Older driver don't support these operations.  | 
|
}  | 
|
ResultSet rs = null;  | 
|
rs = pstmt.executeQuery();  | 
|
ResultSetMetaData rsmd = rs.getMetaData();  | 
|
if (rs.next()) {  | 
|
if (rs.next()) {  | 
|
/** More than one row conflict.  | 
|
* If rs has only one row we are able to  | 
|
* uniquely identify the row where update  | 
|
* have to happen else if more than one  | 
|
* row implies we cannot uniquely identify the row  | 
|
* where we have to do updates.  | 
|
* crs.setKeyColumns needs to be set to  | 
|
* come out of this situation.  | 
|
*/  | 
|
return true;  | 
|
}  | 
|
// don't close the rs  | 
|
// we require the record in rs to be used.  | 
|
// rs.close();  | 
|
                // pstmt.close(); | 
|
rs.first();  | 
|
                // how many fields need to be updated | 
|
int colsNotChanged = 0;  | 
|
Vector<Integer> cols = new Vector<>();  | 
|
String updateExec = updateCmd;  | 
|
Object orig;  | 
|
Object curr;  | 
|
Object rsval;  | 
|
boolean boolNull = true;  | 
|
Object objVal = null;  | 
|
// There's only one row and the cursor  | 
|
// needs to be on that row.  | 
|
boolean first = true;  | 
|
boolean flag = true;  | 
|
this.crsResolve.moveToInsertRow();  | 
|
for (i = 1; i <= callerColumnCount; i++) {  | 
|
orig = origVals.getObject(i);  | 
|
curr = crs.getObject(i);  | 
|
rsval = rs.getObject(i);  | 
|
                /* | 
|
                 * the following block creates equivalent objects | 
|
                 * that would have been created if this rs is populated | 
|
                 * into a CachedRowSet so that comparison of the column values | 
|
                 * from the ResultSet and CachedRowSet are possible | 
|
*/  | 
|
Map<String, Class<?>> map = (crs.getTypeMap() == null)?con.getTypeMap():crs.getTypeMap();  | 
|
if (rsval instanceof Struct) {  | 
|
Struct s = (Struct)rsval;  | 
|
                    // look up the class in the map | 
|
Class<?> c = null;  | 
|
c = map.get(s.getSQLTypeName());  | 
|
if (c != null) {  | 
|
                        // create new instance of the class | 
|
SQLData obj = null;  | 
|
                        try { | 
|
obj = (SQLData)ReflectUtil.newInstance(c);  | 
|
} catch (Exception ex) {  | 
|
throw new SQLException("Unable to Instantiate: ", ex);  | 
|
}  | 
|
                        // get the attributes from the struct | 
|
Object attribs[] = s.getAttributes(map);  | 
|
                        // create the SQLInput "stream" | 
|
SQLInputImpl sqlInput = new SQLInputImpl(attribs, map);  | 
|
                        // read the values... | 
|
obj.readSQL(sqlInput, s.getSQLTypeName());  | 
|
rsval = obj;  | 
|
}  | 
|
} else if (rsval instanceof SQLData) {  | 
|
rsval = new SerialStruct((SQLData)rsval, map);  | 
|
} else if (rsval instanceof Blob) {  | 
|
rsval = new SerialBlob((Blob)rsval);  | 
|
} else if (rsval instanceof Clob) {  | 
|
rsval = new SerialClob((Clob)rsval);  | 
|
} else if (rsval instanceof java.sql.Array) {  | 
|
rsval = new SerialArray((java.sql.Array)rsval, map);  | 
|
}  | 
|
                // reset boolNull if it had been set | 
|
boolNull = true;  | 
|
/** This addtional checking has been added when the current value  | 
|
* in the DB is null, but the DB had a different value when the  | 
|
* data was actaully fetched into the CachedRowSet.  | 
|
**/  | 
|
if(rsval == null && orig != null) {  | 
|
// value in db has changed  | 
|
// don't proceed with synchronization  | 
|
// get the value in db and pass it to the resolver.  | 
|
iChangedValsinDbOnly++;  | 
|
// Set the boolNull to false,  | 
|
                   // in order to set the actual value; | 
|
boolNull = false;  | 
|
objVal = rsval;  | 
|
}  | 
|
/** Adding the checking for rsval to be "not" null or else  | 
|
* it would through a NullPointerException when the values  | 
|
* are compared.  | 
|
**/  | 
|
else if(rsval != null && (!rsval.equals(orig)))  | 
|
                { | 
|
// value in db has changed  | 
|
// don't proceed with synchronization  | 
|
// get the value in db and pass it to the resolver.  | 
|
iChangedValsinDbOnly++;  | 
|
// Set the boolNull to false,  | 
|
                   // in order to set the actual value; | 
|
boolNull = false;  | 
|
objVal = rsval;  | 
|
} else if ( (orig == null || curr == null) ) {  | 
|
/** Adding the additonal condition of checking for "flag"  | 
|
* boolean variable, which would otherwise result in  | 
|
* building a invalid query, as the comma would not be  | 
|
* added to the query string.  | 
|
**/  | 
|
if (first == false || flag == false) {  | 
|
updateExec += ", ";  | 
|
}  | 
|
updateExec += crs.getMetaData().getColumnName(i);  | 
|
cols.add(i);  | 
|
updateExec += " = ? ";  | 
|
first = false;  | 
|
/** Adding the extra condition for orig to be "not" null as the  | 
|
* condition for orig to be null is take prior to this, if this  | 
|
* is not added it will result in a NullPointerException when  | 
|
* the values are compared.  | 
|
**/  | 
|
} else if (orig.equals(curr)) {  | 
|
colsNotChanged++;  | 
|
//nothing to update in this case since values are equal  | 
|
/** Adding the extra condition for orig to be "not" null as the  | 
|
* condition for orig to be null is take prior to this, if this  | 
|
* is not added it will result in a NullPointerException when  | 
|
* the values are compared.  | 
|
**/  | 
|
} else if(orig.equals(curr) == false) {  | 
|
// When values from db and values in CachedRowSet are not equal,  | 
|
// if db value is same as before updation for each col in  | 
|
// the row before fetching into CachedRowSet,  | 
|
// only then we go ahead with updation, else we  | 
|
// throw SyncProviderException.  | 
|
// if value has changed in db after fetching from db  | 
|
// for some cols of the row and at the same time, some other cols  | 
|
// have changed in CachedRowSet, no synchronization happens  | 
|
// Synchronization happens only when data when fetching is  | 
|
// same or at most has changed in cachedrowset  | 
|
// check orig value with what is there in crs for a column  | 
|
// before updation in crs.  | 
|
if(crs.columnUpdated(i)) {  | 
|
if(rsval.equals(orig)) {  | 
|
// At this point we are sure that  | 
|
// the value updated in crs was from  | 
|
                               // what is in db now and has not changed | 
|
if (flag == false || first == false) {  | 
|
updateExec += ", ";  | 
|
}  | 
|
updateExec += crs.getMetaData().getColumnName(i);  | 
|
cols.add(i);  | 
|
updateExec += " = ? ";  | 
|
flag = false;  | 
|
                             } else { | 
|
// Here the value has changed in the db after  | 
|
// data was fetched  | 
|
// Plus store this row from CachedRowSet and keep it  | 
|
                               // in a new CachedRowSet | 
|
boolNull= false;  | 
|
objVal = rsval;  | 
|
iChangedValsInDbAndCRS++;  | 
|
}  | 
|
}  | 
|
}  | 
|
if(!boolNull) {  | 
|
this.crsResolve.updateObject(i,objVal);  | 
|
                                 } else { | 
|
this.crsResolve.updateNull(i);  | 
|
}  | 
|
} //end for  | 
|
rs.close();  | 
|
pstmt.close();  | 
|
this.crsResolve.insertRow();  | 
|
this.crsResolve.moveToCurrentRow();  | 
|
                /** | 
|
                 * if nothing has changed return now - this can happen | 
|
                 * if column is updated to the same value. | 
|
                 * if colsNotChanged == callerColumnCount implies we are updating | 
|
                 * the database with ALL COLUMNS HAVING SAME VALUES, | 
|
                 * so skip going to database, else do as usual. | 
|
**/  | 
|
if ( (first == false && cols.size() == 0) ||  | 
|
colsNotChanged == callerColumnCount ) {  | 
|
return false;  | 
|
}  | 
|
if(iChangedValsInDbAndCRS != 0 || iChangedValsinDbOnly != 0) {  | 
|
return true;  | 
|
}  | 
|
updateExec += updateWhere;  | 
|
pstmt = con.prepareStatement(updateExec);  | 
|
                // Comments needed here | 
|
for (i = 0; i < cols.size(); i++) {  | 
|
Object obj = crs.getObject(cols.get(i));  | 
|
if (obj != null)  | 
|
pstmt.setObject(i + 1, obj);  | 
|
else  | 
|
pstmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));  | 
|
}  | 
|
idx = i;  | 
|
                // Comments needed here | 
|
for (i = 0; i < keyCols.length; i++) {  | 
|
if (params[i] != null) {  | 
|
pstmt.setObject(++idx, params[i]);  | 
|
                    } else { | 
|
continue;  | 
|
}  | 
|
}  | 
|
i = pstmt.executeUpdate();  | 
|
/**  | 
|
* i should be equal to 1(row count), because we update  | 
|
* one row(returned as row count) at a time, if all goes well.  | 
|
* if 1 != 1, this implies we have not been able to  | 
|
* do updations properly i.e there is a conflict in database  | 
|
* versus what is in CachedRowSet for this particular row.  | 
|
**/  | 
|
return false;  | 
|
            } else { | 
|
                /** | 
|
                 * Cursor will be here, if the ResultSet may not return even a single row | 
|
                 * i.e. we can't find the row where to update because it has been deleted | 
|
                 * etc. from the db. | 
|
                 * Present the whole row as null to user, to force null to be sync'ed | 
|
                 * and hence nothing to be synced. | 
|
                 * | 
|
                 * NOTE: | 
|
                 * ------ | 
|
                 * In the database if a column that is mapped to java.sql.Types.REAL stores | 
|
                 * a Double value and is compared with value got from ResultSet.getFloat() | 
|
                 * no row is retrieved and will throw a SyncProviderException. For details | 
|
                 * see bug Id 5053830 | 
|
**/  | 
|
return true;  | 
|
}  | 
|
} catch (SQLException ex) {  | 
|
ex.printStackTrace();  | 
|
// if executeUpdate fails it will come here,  | 
|
            // update crsResolve with null rows | 
|
this.crsResolve.moveToInsertRow();  | 
|
for(i = 1; i <= callerColumnCount; i++) {  | 
|
this.crsResolve.updateNull(i);  | 
|
}  | 
|
this.crsResolve.insertRow();  | 
|
this.crsResolve.moveToCurrentRow();  | 
|
return true;  | 
|
}  | 
|
}  | 
|
   /** | 
|
    * Inserts a row that has been inserted into the given | 
|
    * <code>CachedRowSet</code> object into the data source from which | 
|
    * the rowset is derived, returning <code>false</code> if the insertion | 
|
    * was successful. | 
|
    * | 
|
    * @param crs the <code>CachedRowSet</code> object that has had a row inserted | 
|
    *            and to whose underlying data source the row will be inserted | 
|
    * @param pstmt the <code>PreparedStatement</code> object that will be used | 
|
    *              to execute the insertion | 
|
    * @return <code>false</code> to indicate that the insertion was successful; | 
|
    *         <code>true</code> otherwise | 
|
    * @throws SQLException if a database access error occurs | 
|
*/  | 
|
private boolean insertNewRow(CachedRowSet crs,  | 
|
PreparedStatement pstmt, CachedRowSetImpl crsRes) throws SQLException {  | 
|
boolean returnVal = false;  | 
|
try (PreparedStatement pstmtSel = con.prepareStatement(selectCmd,  | 
|
ResultSet.TYPE_SCROLL_SENSITIVE,  | 
|
ResultSet.CONCUR_READ_ONLY);  | 
|
ResultSet rs = pstmtSel.executeQuery();  | 
|
ResultSet rs2 = con.getMetaData().getPrimaryKeys(null, null,  | 
|
crs.getTableName())  | 
|
       ) { | 
|
ResultSetMetaData rsmd = crs.getMetaData();  | 
|
int icolCount = rsmd.getColumnCount();  | 
|
String[] primaryKeys = new String[icolCount];  | 
|
int k = 0;  | 
|
while (rs2.next()) {  | 
|
primaryKeys[k] = rs2.getString("COLUMN_NAME");  | 
|
k++;  | 
|
}  | 
|
if (rs.next()) {  | 
|
for (String pkName : primaryKeys) {  | 
|
if (!isPKNameValid(pkName, rsmd)) {  | 
|
                       /* We came here as one of the the primary keys | 
|
                        * of the table is not present in the cached | 
|
                        * rowset object, it should be an autoincrement column | 
|
                        * and not included while creating CachedRowSet | 
|
                        * Object, proceed to check for other primary keys | 
|
*/  | 
|
continue;  | 
|
}  | 
|
Object crsPK = crs.getObject(pkName);  | 
|
if (crsPK == null) {  | 
|
                       /* | 
|
                        * It is possible that the PK is null on some databases | 
|
                        * and will be filled in at insert time (MySQL for example) | 
|
*/  | 
|
break;  | 
|
}  | 
|
String rsPK = rs.getObject(pkName).toString();  | 
|
if (crsPK.toString().equals(rsPK)) {  | 
|
returnVal = true;  | 
|
this.crsResolve.moveToInsertRow();  | 
|
for (int i = 1; i <= icolCount; i++) {  | 
|
String colname = (rs.getMetaData()).getColumnName(i);  | 
|
if (colname.equals(pkName))  | 
|
this.crsResolve.updateObject(i,rsPK);  | 
|
else  | 
|
this.crsResolve.updateNull(i);  | 
|
}  | 
|
this.crsResolve.insertRow();  | 
|
this.crsResolve.moveToCurrentRow();  | 
|
}  | 
|
}  | 
|
}  | 
|
if (returnVal) {  | 
|
return returnVal;  | 
|
}  | 
|
           try { | 
|
for (int i = 1; i <= icolCount; i++) {  | 
|
Object obj = crs.getObject(i);  | 
|
if (obj != null) {  | 
|
pstmt.setObject(i, obj);  | 
|
                   } else { | 
|
pstmt.setNull(i,crs.getMetaData().getColumnType(i));  | 
|
}  | 
|
}  | 
|
pstmt.executeUpdate();  | 
|
return false;  | 
|
} catch (SQLException ex) {  | 
|
               /* | 
|
                * Cursor will come here if executeUpdate fails. | 
|
                * There can be many reasons why the insertion failed, | 
|
                * one can be violation of primary key. | 
|
                * Hence we cannot exactly identify why the insertion failed, | 
|
                * present the current row as a null row to the caller. | 
|
*/  | 
|
this.crsResolve.moveToInsertRow();  | 
|
for (int i = 1; i <= icolCount; i++) {  | 
|
this.crsResolve.updateNull(i);  | 
|
}  | 
|
this.crsResolve.insertRow();  | 
|
this.crsResolve.moveToCurrentRow();  | 
|
return true;  | 
|
}  | 
|
}  | 
|
}  | 
|
/** | 
|
 * Deletes the row in the underlying data source that corresponds to | 
|
 * a row that has been deleted in the given <code> CachedRowSet</code> object | 
|
 * and returns <code>false</code> if the deletion was successful. | 
|
 * <P> | 
|
 * This method is called internally by this writer's <code>writeData</code> | 
|
 * method when a row in the rowset has been deleted. The values in the | 
|
 * deleted row are the same as those that are stored in the original row | 
|
 * of the given <code>CachedRowSet</code> object.  If the values in the | 
|
 * original row differ from the row in the underlying data source, the row | 
|
 * in the data source is not deleted, and <code>deleteOriginalRow</code> | 
|
 * returns <code>true</code> to indicate that there was a conflict. | 
|
 * | 
|
 * | 
|
 * @return <code>false</code> if the deletion was successful, which means that | 
|
 *         there was no conflict; <code>true</code> otherwise | 
|
 * @throws SQLException if there was a database access error | 
|
*/  | 
|
private boolean deleteOriginalRow(CachedRowSet crs, CachedRowSetImpl crsRes) throws SQLException {  | 
|
PreparedStatement pstmt;  | 
|
int i;  | 
|
int idx = 0;  | 
|
String strSelect;  | 
|
    // Select the row from the database. | 
|
ResultSet origVals = crs.getOriginalRow();  | 
|
origVals.next();  | 
|
deleteWhere = buildWhereClause(deleteWhere, origVals);  | 
|
pstmt = con.prepareStatement(selectCmd + deleteWhere,  | 
|
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);  | 
|
for (i = 0; i < keyCols.length; i++) {  | 
|
if (params[i] != null) {  | 
|
pstmt.setObject(++idx, params[i]);  | 
|
            } else { | 
|
continue;  | 
|
}  | 
|
}  | 
|
        try { | 
|
pstmt.setMaxRows(crs.getMaxRows());  | 
|
pstmt.setMaxFieldSize(crs.getMaxFieldSize());  | 
|
pstmt.setEscapeProcessing(crs.getEscapeProcessing());  | 
|
pstmt.setQueryTimeout(crs.getQueryTimeout());  | 
|
} catch (Exception ex) {  | 
|
            /* | 
|
             * Older driver don't support these operations... | 
|
*/  | 
|
;  | 
|
}  | 
|
ResultSet rs = pstmt.executeQuery();  | 
|
if (rs.next() == true) {  | 
|
if (rs.next()) {  | 
|
                // more than one row | 
|
return true;  | 
|
}  | 
|
rs.first();  | 
|
// Now check all the values in rs to be same in  | 
|
            // db also before actually going ahead with deleting | 
|
boolean boolChanged = false;  | 
|
crsRes.moveToInsertRow();  | 
|
for (i = 1; i <= crs.getMetaData().getColumnCount(); i++) {  | 
|
Object original = origVals.getObject(i);  | 
|
Object changed = rs.getObject(i);  | 
|
if(original != null && changed != null ) {  | 
|
if(! (original.toString()).equals(changed.toString()) ) {  | 
|
boolChanged = true;  | 
|
crsRes.updateObject(i,origVals.getObject(i));  | 
|
}  | 
|
                } else { | 
|
crsRes.updateNull(i);  | 
|
}  | 
|
}  | 
|
crsRes.insertRow();  | 
|
crsRes.moveToCurrentRow();  | 
|
if(boolChanged) {  | 
|
// do not delete as values in db have changed  | 
|
// deletion will not happen for this row from db  | 
|
                   // exit now returning true. i.e. conflict | 
|
return true;  | 
|
            } else { | 
|
// delete the row.  | 
|
// Go ahead with deleting,  | 
|
// don't do anything here  | 
|
}  | 
|
String cmd = deleteCmd + deleteWhere;  | 
|
pstmt = con.prepareStatement(cmd);  | 
|
idx = 0;  | 
|
for (i = 0; i < keyCols.length; i++) {  | 
|
if (params[i] != null) {  | 
|
pstmt.setObject(++idx, params[i]);  | 
|
                } else { | 
|
continue;  | 
|
}  | 
|
}  | 
|
if (pstmt.executeUpdate() != 1) {  | 
|
return true;  | 
|
}  | 
|
pstmt.close();  | 
|
        } else { | 
|
            // didn't find the row | 
|
return true;  | 
|
}  | 
|
        // no conflict | 
|
return false;  | 
|
}  | 
|
    /** | 
|
     * Sets the reader for this writer to the given reader. | 
|
     * | 
|
     * @throws SQLException if a database access error occurs | 
|
*/  | 
|
public void setReader(CachedRowSetReader reader) throws SQLException {  | 
|
this.reader = reader;  | 
|
}  | 
|
    /** | 
|
     * Gets the reader for this writer. | 
|
     * | 
|
     * @throws SQLException if a database access error occurs | 
|
*/  | 
|
public CachedRowSetReader getReader() throws SQLException {  | 
|
return reader;  | 
|
}  | 
|
    /** | 
|
     * Composes a <code>SELECT</code>, <code>UPDATE</code>, <code>INSERT</code>, | 
|
     * and <code>DELETE</code> statement that can be used by this writer to | 
|
     * write data to the data source backing the given <code>CachedRowSet</code> | 
|
     * object. | 
|
     * | 
|
     * @ param caller a <code>CachedRowSet</code> object for which this | 
|
     *                <code>CachedRowSetWriter</code> object is the writer | 
|
     * @throws SQLException if a database access error occurs | 
|
*/  | 
|
private void initSQLStatements(CachedRowSet caller) throws SQLException {  | 
|
int i;  | 
|
callerMd = caller.getMetaData();  | 
|
callerColumnCount = callerMd.getColumnCount();  | 
|
if (callerColumnCount < 1)  | 
|
            // No data, so return. | 
|
return;  | 
|
        /* | 
|
         * If the RowSet has a Table name we should use it. | 
|
         * This is really a hack to get round the fact that | 
|
         * a lot of the jdbc drivers can't provide the tab. | 
|
*/  | 
|
String table = caller.getTableName();  | 
|
if (table == null) {  | 
|
            /* | 
|
             * attempt to build a table name using the info | 
|
             * that the driver gave us for the first column | 
|
             * in the source result set. | 
|
*/  | 
|
table = callerMd.getTableName(1);  | 
|
if (table == null || table.length() == 0) {  | 
|
throw new SQLException(resBundle.handleGetObject("crswriter.tname").toString());  | 
|
}  | 
|
}  | 
|
String catalog = callerMd.getCatalogName(1);  | 
|
String schema = callerMd.getSchemaName(1);  | 
|
DatabaseMetaData dbmd = con.getMetaData();  | 
|
/*  | 
|
* Compose a SELECT statement. There are three parts.  | 
|
*/  | 
|
        // Project List | 
|
selectCmd = "SELECT ";  | 
|
for (i=1; i <= callerColumnCount; i++) {  | 
|
selectCmd += callerMd.getColumnName(i);  | 
|
if ( i < callerMd.getColumnCount() )  | 
|
selectCmd += ", ";  | 
|
else  | 
|
selectCmd += " ";  | 
|
}  | 
|
        // FROM clause. | 
|
selectCmd += "FROM " + buildTableName(dbmd, catalog, schema, table);  | 
|
        /* | 
|
         * Compose an UPDATE statement. | 
|
*/  | 
|
updateCmd = "UPDATE " + buildTableName(dbmd, catalog, schema, table);  | 
|
/**  | 
|
* 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 tempupdCmd = updateCmd.toLowerCase();  | 
|
int idxupWhere = tempupdCmd.indexOf("where");  | 
|
if(idxupWhere != -1)  | 
|
        { | 
|
updateCmd = updateCmd.substring(0,idxupWhere);  | 
|
}  | 
|
updateCmd += "SET ";  | 
|
        /* | 
|
         * Compose an INSERT statement. | 
|
*/  | 
|
insertCmd = "INSERT INTO " + buildTableName(dbmd, catalog, schema, table);  | 
|
        // Column list | 
|
insertCmd += "(";  | 
|
for (i=1; i <= callerColumnCount; i++) {  | 
|
insertCmd += callerMd.getColumnName(i);  | 
|
if ( i < callerMd.getColumnCount() )  | 
|
insertCmd += ", ";  | 
|
else  | 
|
insertCmd += ") VALUES (";  | 
|
}  | 
|
for (i=1; i <= callerColumnCount; i++) {  | 
|
insertCmd += "?";  | 
|
if (i < callerColumnCount)  | 
|
insertCmd += ", ";  | 
|
else  | 
|
insertCmd += ")";  | 
|
}  | 
|
        /* | 
|
         * Compose a DELETE statement. | 
|
*/  | 
|
deleteCmd = "DELETE FROM " + buildTableName(dbmd, catalog, schema, table);  | 
|
        /* | 
|
         * set the key desriptors that will be | 
|
         * needed to construct where clauses. | 
|
*/  | 
|
buildKeyDesc(caller);  | 
|
}  | 
|
    /** | 
|
     * Returns a fully qualified table name built from the given catalog and | 
|
     * table names. The given metadata object is used to get the proper order | 
|
     * and separator. | 
|
     * | 
|
     * @param dbmd a <code>DatabaseMetaData</code> object that contains metadata | 
|
     *          about this writer's <code>CachedRowSet</code> object | 
|
     * @param catalog a <code>String</code> object with the rowset's catalog | 
|
     *          name | 
|
     * @param table a <code>String</code> object with the name of the table from | 
|
     *          which this writer's rowset was derived | 
|
     * @return a <code>String</code> object with the fully qualified name of the | 
|
     *          table from which this writer's rowset was derived | 
|
     * @throws SQLException if a database access error occurs | 
|
*/  | 
|
private String buildTableName(DatabaseMetaData dbmd,  | 
|
String catalog, String schema, String table) throws SQLException {  | 
|
// trim all the leading and trailing whitespaces,  | 
|
// white spaces can never be catalog, schema or a table name.  | 
|
String cmd = "";  | 
|
catalog = catalog.trim();  | 
|
schema = schema.trim();  | 
|
table = table.trim();  | 
|
if (dbmd.isCatalogAtStart() == true) {  | 
|
if (catalog != null && catalog.length() > 0) {  | 
|
cmd += catalog + dbmd.getCatalogSeparator();  | 
|
}  | 
|
if (schema != null && schema.length() > 0) {  | 
|
cmd += schema + ".";  | 
|
}  | 
|
cmd += table;  | 
|
        } else { | 
|
if (schema != null && schema.length() > 0) {  | 
|
cmd += schema + ".";  | 
|
}  | 
|
cmd += table;  | 
|
if (catalog != null && catalog.length() > 0) {  | 
|
cmd += dbmd.getCatalogSeparator() + catalog;  | 
|
}  | 
|
}  | 
|
cmd += " ";  | 
|
return cmd;  | 
|
}  | 
|
    /** | 
|
     * Assigns to the given <code>CachedRowSet</code> object's | 
|
     * <code>params</code> | 
|
     * field an array whose length equals the number of columns needed | 
|
     * to uniquely identify a row in the rowset. The array is given | 
|
     * values by the method <code>buildWhereClause</code>. | 
|
     * <P> | 
|
     * If the <code>CachedRowSet</code> object's <code>keyCols</code> | 
|
     * field has length <code>0</code> or is <code>null</code>, the array | 
|
     * is set with the column number of every column in the rowset. | 
|
     * Otherwise, the array in the field <code>keyCols</code> is set with only | 
|
     * the column numbers of the columns that are required to form a unique | 
|
     * identifier for a row. | 
|
     * | 
|
     * @param crs the <code>CachedRowSet</code> object for which this | 
|
     *     <code>CachedRowSetWriter</code> object is the writer | 
|
     * | 
|
     * @throws SQLException if a database access error occurs | 
|
*/  | 
|
private void buildKeyDesc(CachedRowSet crs) throws SQLException {  | 
|
keyCols = crs.getKeyColumns();  | 
|
ResultSetMetaData resultsetmd = crs.getMetaData();  | 
|
if (keyCols == null || keyCols.length == 0) {  | 
|
ArrayList<Integer> listKeys = new ArrayList<Integer>();  | 
|
for (int i = 0; i < callerColumnCount; i++ ) {  | 
|
if(resultsetmd.getColumnType(i+1) != java.sql.Types.CLOB &&  | 
|
resultsetmd.getColumnType(i+1) != java.sql.Types.STRUCT &&  | 
|
resultsetmd.getColumnType(i+1) != java.sql.Types.SQLXML &&  | 
|
resultsetmd.getColumnType(i+1) != java.sql.Types.BLOB &&  | 
|
resultsetmd.getColumnType(i+1) != java.sql.Types.ARRAY &&  | 
|
resultsetmd.getColumnType(i+1) != java.sql.Types.OTHER )  | 
|
listKeys.add(i+1);  | 
|
}  | 
|
keyCols = new int[listKeys.size()];  | 
|
for (int i = 0; i < listKeys.size(); i++ )  | 
|
keyCols[i] = listKeys.get(i);  | 
|
}  | 
|
params = new Object[keyCols.length];  | 
|
}  | 
|
    /** | 
|
         * Constructs an SQL <code>WHERE</code> clause using the given | 
|
         * string as a starting point. The resulting clause will contain | 
|
         * a column name and " = ?" for each key column, that is, each column | 
|
         * that is needed to form a unique identifier for a row in the rowset. | 
|
         * This <code>WHERE</code> clause can be added to | 
|
         * a <code>PreparedStatement</code> object that updates, inserts, or | 
|
         * deletes a row. | 
|
         * <P> | 
|
         * This method uses the given result set to access values in the | 
|
         * <code>CachedRowSet</code> object that called this writer.  These | 
|
         * values are used to build the array of parameters that will serve as | 
|
         * replacements for the "?" parameter placeholders in the | 
|
         * <code>PreparedStatement</code> object that is sent to the | 
|
         * <code>CachedRowSet</code> object's underlying data source. | 
|
         * | 
|
         * @param whereClause a <code>String</code> object that is an empty | 
|
         *                    string ("") | 
|
         * @param rs a <code>ResultSet</code> object that can be used | 
|
         *           to access the <code>CachedRowSet</code> object's data | 
|
         * @return a <code>WHERE</code> clause of the form "<code>WHERE</code> | 
|
         *         columnName = ? AND columnName = ? AND columnName = ? ..." | 
|
         * @throws SQLException if a database access error occurs | 
|
*/  | 
|
private String buildWhereClause(String whereClause,  | 
|
ResultSet rs) throws SQLException {  | 
|
whereClause = "WHERE ";  | 
|
for (int i = 0; i < keyCols.length; i++) {  | 
|
if (i > 0) {  | 
|
whereClause += "AND ";  | 
|
}  | 
|
whereClause += callerMd.getColumnName(keyCols[i]);  | 
|
params[i] = rs.getObject(keyCols[i]);  | 
|
if (rs.wasNull() == true) {  | 
|
whereClause += " IS NULL ";  | 
|
            } else { | 
|
whereClause += " = ? ";  | 
|
}  | 
|
}  | 
|
return whereClause;  | 
|
}  | 
|
void updateResolvedConflictToDB(CachedRowSet crs, Connection con) throws SQLException {  | 
|
          //String updateExe = ; | 
|
PreparedStatement pStmt ;  | 
|
String strWhere = "WHERE " ;  | 
|
String strExec =" ";  | 
|
String strUpdate = "UPDATE ";  | 
|
int icolCount = crs.getMetaData().getColumnCount();  | 
|
int keyColumns[] = crs.getKeyColumns();  | 
|
Object param[];  | 
|
String strSet="";  | 
|
strWhere = buildWhereClause(strWhere, crs);  | 
|
if (keyColumns == null || keyColumns.length == 0) {  | 
|
keyColumns = new int[icolCount];  | 
|
for (int i = 0; i < keyColumns.length; ) {  | 
|
keyColumns[i] = ++i;  | 
|
}  | 
|
}  | 
|
param = new Object[keyColumns.length];  | 
|
strUpdate = "UPDATE " + buildTableName(con.getMetaData(),  | 
|
crs.getMetaData().getCatalogName(1),  | 
|
crs.getMetaData().getSchemaName(1),  | 
|
crs.getTableName());  | 
|
// changed or updated values will become part of  | 
|
         // set clause here | 
|
strUpdate += "SET ";  | 
|
boolean first = true;  | 
|
for (int i=1; i<=icolCount;i++) {  | 
|
if (crs.columnUpdated(i)) {  | 
|
if (first == false) {  | 
|
strSet += ", ";  | 
|
}  | 
|
strSet += crs.getMetaData().getColumnName(i);  | 
|
strSet += " = ? ";  | 
|
first = false;  | 
|
} //end if  | 
|
} //end for  | 
|
         // keycols will become part of where clause | 
|
strUpdate += strSet;  | 
|
strWhere = "WHERE ";  | 
|
for (int i = 0; i < keyColumns.length; i++) {  | 
|
if (i > 0) {  | 
|
strWhere += "AND ";  | 
|
}  | 
|
strWhere += crs.getMetaData().getColumnName(keyColumns[i]);  | 
|
param[i] = crs.getObject(keyColumns[i]);  | 
|
if (crs.wasNull() == true) {  | 
|
strWhere += " IS NULL ";  | 
|
            } else { | 
|
strWhere += " = ? ";  | 
|
}  | 
|
}  | 
|
strUpdate += strWhere;  | 
|
pStmt = con.prepareStatement(strUpdate);  | 
|
int idx =0;  | 
|
for (int i = 0; i < icolCount; i++) {  | 
|
if(crs.columnUpdated(i+1)) {  | 
|
Object obj = crs.getObject(i+1);  | 
|
if (obj != null) {  | 
|
pStmt.setObject(++idx, obj);  | 
|
              } else { | 
|
pStmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));  | 
|
} //end if ..else  | 
|
} //end if crs.column...  | 
|
} //end for  | 
|
          // Set the key cols for after WHERE =? clause | 
|
for (int i = 0; i < keyColumns.length; i++) {  | 
|
if (param[i] != null) {  | 
|
pStmt.setObject(++idx, param[i]);  | 
|
}  | 
|
}  | 
|
int id = pStmt.executeUpdate();  | 
|
}  | 
|
    /** | 
|
     * | 
|
*/  | 
|
public void commit() throws SQLException {  | 
|
con.commit();  | 
|
if (reader.getCloseConnection() == true) {  | 
|
con.close();  | 
|
}  | 
|
}  | 
|
public void commit(CachedRowSetImpl crs, boolean updateRowset) throws SQLException {  | 
|
con.commit();  | 
|
if(updateRowset) {  | 
|
if(crs.getCommand() != null)  | 
|
crs.execute(con);  | 
|
}  | 
|
if (reader.getCloseConnection() == true) {  | 
|
con.close();  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * | 
|
*/  | 
|
public void rollback() throws SQLException {  | 
|
con.rollback();  | 
|
if (reader.getCloseConnection() == true) {  | 
|
con.close();  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * | 
|
*/  | 
|
public void rollback(Savepoint s) throws SQLException {  | 
|
con.rollback(s);  | 
|
if (reader.getCloseConnection() == true) {  | 
|
con.close();  | 
|
}  | 
|
}  | 
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {  | 
|
        // Default state initialization happens here | 
|
ois.defaultReadObject();  | 
|
        // Initialization of  Res Bundle happens here . | 
|
        try { | 
|
resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();  | 
|
} catch(IOException ioe) {  | 
|
throw new RuntimeException(ioe);  | 
|
}  | 
|
}  | 
|
static final long serialVersionUID =-8506030970299413976L;  | 
|
    /** | 
|
     * Validate whether the Primary Key is known to the CachedRowSet.  If it is | 
|
     * not, it is an auto-generated key | 
|
     * @param pk - Primary Key to validate | 
|
     * @param rsmd - ResultSetMetadata for the RowSet | 
|
     * @return true if found, false otherwise (auto generated key) | 
|
*/  | 
|
private boolean isPKNameValid(String pk, ResultSetMetaData rsmd) throws SQLException {  | 
|
boolean isValid = false;  | 
|
int cols = rsmd.getColumnCount();  | 
|
for(int i = 1; i<= cols; i++) {  | 
|
String colName = rsmd.getColumnClassName(i);  | 
|
if(colName.equalsIgnoreCase(pk)) {  | 
|
isValid = true;  | 
|
break;  | 
|
}  | 
|
}  | 
|
return isValid;  | 
|
}  | 
|
}  |