Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java
38920 views
/*1* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.rowset.internal;2627import java.sql.*;28import javax.sql.*;29import java.util.*;30import java.io.*;31import sun.reflect.misc.ReflectUtil;3233import com.sun.rowset.*;34import java.text.MessageFormat;35import javax.sql.rowset.*;36import javax.sql.rowset.serial.SQLInputImpl;37import javax.sql.rowset.serial.SerialArray;38import javax.sql.rowset.serial.SerialBlob;39import javax.sql.rowset.serial.SerialClob;40import javax.sql.rowset.serial.SerialStruct;41import javax.sql.rowset.spi.*;424344/**45* The facility called on internally by the <code>RIOptimisticProvider</code> implementation to46* propagate changes back to the data source from which the rowset got its data.47* <P>48* A <code>CachedRowSetWriter</code> object, called a writer, has the public49* method <code>writeData</code> for writing modified data to the underlying data source.50* This method is invoked by the rowset internally and is never invoked directly by an application.51* A writer also has public methods for setting and getting52* the <code>CachedRowSetReader</code> object, called a reader, that is associated53* with the writer. The remainder of the methods in this class are private and54* are invoked internally, either directly or indirectly, by the method55* <code>writeData</code>.56* <P>57* Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and58* the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects.59* Standard JDBC RowSet implementations provide an object instance of this60* writer by invoking the <code>SyncProvider.getRowSetWriter()</code> method.61*62* @version 0.263* @author Jonathan Bruce64* @see javax.sql.rowset.spi.SyncProvider65* @see javax.sql.rowset.spi.SyncFactory66* @see javax.sql.rowset.spi.SyncFactoryException67*/68public class CachedRowSetWriter implements TransactionalWriter, Serializable {6970/**71* The <code>Connection</code> object that this writer will use to make a72* connection to the data source to which it will write data.73*74*/75private transient Connection con;7677/**78* The SQL <code>SELECT</code> command that this writer will call79* internally. The method <code>initSQLStatements</code> builds this80* command by supplying the words "SELECT" and "FROM," and using81* metadata to get the table name and column names .82*83* @serial84*/85private String selectCmd;8687/**88* The SQL <code>UPDATE</code> command that this writer will call89* internally to write data to the rowset's underlying data source.90* The method <code>initSQLStatements</code> builds this <code>String</code>91* object.92*93* @serial94*/95private String updateCmd;9697/**98* The SQL <code>WHERE</code> clause the writer will use for update99* statements in the <code>PreparedStatement</code> object100* it sends to the underlying data source.101*102* @serial103*/104private String updateWhere;105106/**107* The SQL <code>DELETE</code> command that this writer will call108* internally to delete a row in the rowset's underlying data source.109*110* @serial111*/112private String deleteCmd;113114/**115* The SQL <code>WHERE</code> clause the writer will use for delete116* statements in the <code>PreparedStatement</code> object117* it sends to the underlying data source.118*119* @serial120*/121private String deleteWhere;122123/**124* The SQL <code>INSERT INTO</code> command that this writer will internally use125* to insert data into the rowset's underlying data source. The method126* <code>initSQLStatements</code> builds this command with a question127* mark parameter placeholder for each column in the rowset.128*129* @serial130*/131private String insertCmd;132133/**134* An array containing the column numbers of the columns that are135* needed to uniquely identify a row in the <code>CachedRowSet</code> object136* for which this <code>CachedRowSetWriter</code> object is the writer.137*138* @serial139*/140private int[] keyCols;141142/**143* An array of the parameters that should be used to set the parameter144* placeholders in a <code>PreparedStatement</code> object that this145* writer will execute.146*147* @serial148*/149private Object[] params;150151/**152* The <code>CachedRowSetReader</code> object that has been153* set as the reader for the <code>CachedRowSet</code> object154* for which this <code>CachedRowSetWriter</code> object is the writer.155*156* @serial157*/158private CachedRowSetReader reader;159160/**161* The <code>ResultSetMetaData</code> object that contains information162* about the columns in the <code>CachedRowSet</code> object163* for which this <code>CachedRowSetWriter</code> object is the writer.164*165* @serial166*/167private ResultSetMetaData callerMd;168169/**170* The number of columns in the <code>CachedRowSet</code> object171* for which this <code>CachedRowSetWriter</code> object is the writer.172*173* @serial174*/175private int callerColumnCount;176177/**178* This <code>CachedRowSet<code> will hold the conflicting values179* retrieved from the db and hold it.180*/181private CachedRowSetImpl crsResolve;182183/**184* This <code>ArrayList<code> will hold the values of SyncResolver.*185*/186private ArrayList<Integer> status;187188/**189* This will check whether the same field value has changed both190* in database and CachedRowSet.191*/192private int iChangedValsInDbAndCRS;193194/**195* This will hold the number of cols for which the values have196* changed only in database.197*/198private int iChangedValsinDbOnly ;199200private JdbcRowSetResourceBundle resBundle;201202public CachedRowSetWriter() {203try {204resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();205} catch(IOException ioe) {206throw new RuntimeException(ioe);207}208}209210/**211* Propagates changes in the given <code>RowSet</code> object212* back to its underlying data source and returns <code>true</code>213* if successful. The writer will check to see if214* the data in the pre-modified rowset (the original values) differ215* from the data in the underlying data source. If data in the data216* source has been modified by someone else, there is a conflict,217* and in that case, the writer will not write to the data source.218* In other words, the writer uses an optimistic concurrency algorithm:219* It checks for conflicts before making changes rather than restricting220* access for concurrent users.221* <P>222* This method is called by the rowset internally when223* the application invokes the method <code>acceptChanges</code>.224* The <code>writeData</code> method in turn calls private methods that225* it defines internally.226* The following is a general summary of what the method227* <code>writeData</code> does, much of which is accomplished228* through calls to its own internal methods.229* <OL>230* <LI>Creates a <code>CachedRowSet</code> object from the given231* <code>RowSet</code> object232* <LI>Makes a connection with the data source233* <UL>234* <LI>Disables autocommit mode if it is not already disabled235* <LI>Sets the transaction isolation level to that of the rowset236* </UL>237* <LI>Checks to see if the reader has read new data since the writer238* was last called and, if so, calls the method239* <code>initSQLStatements</code> to initialize new SQL statements240* <UL>241* <LI>Builds new <code>SELECT</code>, <code>UPDATE</code>,242* <code>INSERT</code>, and <code>DELETE</code> statements243* <LI>Uses the <code>CachedRowSet</code> object's metadata to244* determine the table name, column names, and the columns245* that make up the primary key246* </UL>247* <LI>When there is no conflict, propagates changes made to the248* <code>CachedRowSet</code> object back to its underlying data source249* <UL>250* <LI>Iterates through each row of the <code>CachedRowSet</code> object251* to determine whether it has been updated, inserted, or deleted252* <LI>If the corresponding row in the data source has not been changed253* since the rowset last read its254* values, the writer will use the appropriate command to update,255* insert, or delete the row256* <LI>If any data in the data source does not match the original values257* for the <code>CachedRowSet</code> object, the writer will roll258* back any changes it has made to the row in the data source.259* </UL>260* </OL>261*262* @return <code>true</code> if changes to the rowset were successfully263* written to the rowset's underlying data source;264* <code>false</code> otherwise265*/266public boolean writeData(RowSetInternal caller) throws SQLException {267long conflicts = 0;268boolean showDel = false;269PreparedStatement pstmtIns = null;270iChangedValsInDbAndCRS = 0;271iChangedValsinDbOnly = 0;272273// We assume caller is a CachedRowSet274CachedRowSetImpl crs = (CachedRowSetImpl)caller;275// crsResolve = new CachedRowSetImpl();276this.crsResolve = new CachedRowSetImpl();;277278// The reader is registered with the writer at design time.279// This is not required, in general. The reader has logic280// to get a JDBC connection, so call it.281282con = reader.connect(caller);283284285if (con == null) {286throw new SQLException(resBundle.handleGetObject("crswriter.connect").toString());287}288289/*290// Fix 6200646.291// Don't change the connection or transaction properties. This will fail in a292// J2EE container.293if (con.getAutoCommit() == true) {294con.setAutoCommit(false);295}296297con.setTransactionIsolation(crs.getTransactionIsolation());298*/299300initSQLStatements(crs);301int iColCount;302303RowSetMetaDataImpl rsmdWrite = (RowSetMetaDataImpl)crs.getMetaData();304RowSetMetaDataImpl rsmdResolv = new RowSetMetaDataImpl();305306iColCount = rsmdWrite.getColumnCount();307int sz= crs.size()+1;308status = new ArrayList<>(sz);309310status.add(0,null);311rsmdResolv.setColumnCount(iColCount);312313for(int i =1; i <= iColCount; i++) {314rsmdResolv.setColumnType(i, rsmdWrite.getColumnType(i));315rsmdResolv.setColumnName(i, rsmdWrite.getColumnName(i));316rsmdResolv.setNullable(i, ResultSetMetaData.columnNullableUnknown);317}318this.crsResolve.setMetaData(rsmdResolv);319320// moved outside the insert inner loop321//pstmtIns = con.prepareStatement(insertCmd);322323if (callerColumnCount < 1) {324// No data, so return success.325if (reader.getCloseConnection() == true)326con.close();327return true;328}329// We need to see rows marked for deletion.330showDel = crs.getShowDeleted();331crs.setShowDeleted(true);332333// Look at all the rows.334crs.beforeFirst();335336int rows =1;337while (crs.next()) {338if (crs.rowDeleted()) {339// The row has been deleted.340if (deleteOriginalRow(crs, this.crsResolve)) {341status.add(rows, SyncResolver.DELETE_ROW_CONFLICT);342conflicts++;343} else {344// delete happened without any occurrence of conflicts345// so update status accordingly346status.add(rows, SyncResolver.NO_ROW_CONFLICT);347}348349} else if (crs.rowInserted()) {350// The row has been inserted.351352pstmtIns = con.prepareStatement(insertCmd);353if (insertNewRow(crs, pstmtIns, this.crsResolve)) {354status.add(rows, SyncResolver.INSERT_ROW_CONFLICT);355conflicts++;356} else {357// insert happened without any occurrence of conflicts358// so update status accordingly359status.add(rows, SyncResolver.NO_ROW_CONFLICT);360}361} else if (crs.rowUpdated()) {362// The row has been updated.363if (updateOriginalRow(crs)) {364status.add(rows, SyncResolver.UPDATE_ROW_CONFLICT);365conflicts++;366} else {367// update happened without any occurrence of conflicts368// so update status accordingly369status.add(rows, SyncResolver.NO_ROW_CONFLICT);370}371372} else {373/** The row is neither of inserted, updated or deleted.374* So set nulls in the this.crsResolve for this row,375* as nothing is to be done for such rows.376* Also note that if such a row has been changed in database377* and we have not changed(inserted, updated or deleted)378* that is fine.379**/380int icolCount = crs.getMetaData().getColumnCount();381status.add(rows, SyncResolver.NO_ROW_CONFLICT);382383this.crsResolve.moveToInsertRow();384for(int cols=0;cols<iColCount;cols++) {385this.crsResolve.updateNull(cols+1);386} //end for387388this.crsResolve.insertRow();389this.crsResolve.moveToCurrentRow();390391} //end if392rows++;393} //end while394395// close the insert statement396if(pstmtIns!=null)397pstmtIns.close();398// reset399crs.setShowDeleted(showDel);400401crs.beforeFirst();402this.crsResolve.beforeFirst();403404if(conflicts != 0) {405SyncProviderException spe = new SyncProviderException(conflicts + " " +406resBundle.handleGetObject("crswriter.conflictsno").toString());407//SyncResolver syncRes = spe.getSyncResolver();408409SyncResolverImpl syncResImpl = (SyncResolverImpl) spe.getSyncResolver();410411syncResImpl.setCachedRowSet(crs);412syncResImpl.setCachedRowSetResolver(this.crsResolve);413414syncResImpl.setStatus(status);415syncResImpl.setCachedRowSetWriter(this);416417throw spe;418} else {419return true;420}421/*422if (conflict == true) {423con.rollback();424return false;425} else {426con.commit();427if (reader.getCloseConnection() == true) {428con.close();429}430return true;431}432*/433434} //end writeData435436/**437* Updates the given <code>CachedRowSet</code> object's underlying data438* source so that updates to the rowset are reflected in the original439* data source, and returns <code>false</code> if the update was successful.440* A return value of <code>true</code> indicates that there is a conflict,441* meaning that a value updated in the rowset has already been changed by442* someone else in the underlying data source. A conflict can also exist443* if, for example, more than one row in the data source would be affected444* by the update or if no rows would be affected. In any case, if there is445* a conflict, this method does not update the underlying data source.446* <P>447* This method is called internally by the method <code>writeData</code>448* if a row in the <code>CachedRowSet</code> object for which this449* <code>CachedRowSetWriter</code> object is the writer has been updated.450*451* @return <code>false</code> if the update to the underlying data source is452* successful; <code>true</code> otherwise453* @throws SQLException if a database access error occurs454*/455private boolean updateOriginalRow(CachedRowSet crs)456throws SQLException {457PreparedStatement pstmt;458int i = 0;459int idx = 0;460461// Select the row from the database.462ResultSet origVals = crs.getOriginalRow();463origVals.next();464465try {466updateWhere = buildWhereClause(updateWhere, origVals);467468469/**470* The following block of code is for checking a particular type of471* query where in there is a where clause. Without this block, if a472* SQL statement is built the "where" clause will appear twice hence473* the DB errors out and a SQLException is thrown. This code also474* considers that the where clause is in the right place as the475* CachedRowSet object would already have been populated with this476* query before coming to this point.477**/478479480String tempselectCmd = selectCmd.toLowerCase();481482int idxWhere = tempselectCmd.indexOf("where");483484if(idxWhere != -1)485{486String tempSelect = selectCmd.substring(0,idxWhere);487selectCmd = tempSelect;488}489490pstmt = con.prepareStatement(selectCmd + updateWhere,491ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);492493for (i = 0; i < keyCols.length; i++) {494if (params[i] != null) {495pstmt.setObject(++idx, params[i]);496} else {497continue;498}499}500501try {502pstmt.setMaxRows(crs.getMaxRows());503pstmt.setMaxFieldSize(crs.getMaxFieldSize());504pstmt.setEscapeProcessing(crs.getEscapeProcessing());505pstmt.setQueryTimeout(crs.getQueryTimeout());506} catch (Exception ex) {507// Older driver don't support these operations.508}509510ResultSet rs = null;511rs = pstmt.executeQuery();512ResultSetMetaData rsmd = rs.getMetaData();513514if (rs.next()) {515if (rs.next()) {516/** More than one row conflict.517* If rs has only one row we are able to518* uniquely identify the row where update519* have to happen else if more than one520* row implies we cannot uniquely identify the row521* where we have to do updates.522* crs.setKeyColumns needs to be set to523* come out of this situation.524*/525526return true;527}528529// don't close the rs530// we require the record in rs to be used.531// rs.close();532// pstmt.close();533rs.first();534535// how many fields need to be updated536int colsNotChanged = 0;537Vector<Integer> cols = new Vector<>();538String updateExec = updateCmd;539Object orig;540Object curr;541Object rsval;542boolean boolNull = true;543Object objVal = null;544545// There's only one row and the cursor546// needs to be on that row.547548boolean first = true;549boolean flag = true;550551this.crsResolve.moveToInsertRow();552553for (i = 1; i <= callerColumnCount; i++) {554orig = origVals.getObject(i);555curr = crs.getObject(i);556rsval = rs.getObject(i);557/*558* the following block creates equivalent objects559* that would have been created if this rs is populated560* into a CachedRowSet so that comparison of the column values561* from the ResultSet and CachedRowSet are possible562*/563Map<String, Class<?>> map = (crs.getTypeMap() == null)?con.getTypeMap():crs.getTypeMap();564if (rsval instanceof Struct) {565566Struct s = (Struct)rsval;567568// look up the class in the map569Class<?> c = null;570c = map.get(s.getSQLTypeName());571if (c != null) {572// create new instance of the class573SQLData obj = null;574try {575obj = (SQLData)ReflectUtil.newInstance(c);576} catch (Exception ex) {577throw new SQLException("Unable to Instantiate: ", ex);578}579// get the attributes from the struct580Object attribs[] = s.getAttributes(map);581// create the SQLInput "stream"582SQLInputImpl sqlInput = new SQLInputImpl(attribs, map);583// read the values...584obj.readSQL(sqlInput, s.getSQLTypeName());585rsval = obj;586}587} else if (rsval instanceof SQLData) {588rsval = new SerialStruct((SQLData)rsval, map);589} else if (rsval instanceof Blob) {590rsval = new SerialBlob((Blob)rsval);591} else if (rsval instanceof Clob) {592rsval = new SerialClob((Clob)rsval);593} else if (rsval instanceof java.sql.Array) {594rsval = new SerialArray((java.sql.Array)rsval, map);595}596597// reset boolNull if it had been set598boolNull = true;599600/** This addtional checking has been added when the current value601* in the DB is null, but the DB had a different value when the602* data was actaully fetched into the CachedRowSet.603**/604605if(rsval == null && orig != null) {606// value in db has changed607// don't proceed with synchronization608// get the value in db and pass it to the resolver.609610iChangedValsinDbOnly++;611// Set the boolNull to false,612// in order to set the actual value;613boolNull = false;614objVal = rsval;615}616617/** Adding the checking for rsval to be "not" null or else618* it would through a NullPointerException when the values619* are compared.620**/621622else if(rsval != null && (!rsval.equals(orig)))623{624// value in db has changed625// don't proceed with synchronization626// get the value in db and pass it to the resolver.627628iChangedValsinDbOnly++;629// Set the boolNull to false,630// in order to set the actual value;631boolNull = false;632objVal = rsval;633} else if ( (orig == null || curr == null) ) {634635/** Adding the additonal condition of checking for "flag"636* boolean variable, which would otherwise result in637* building a invalid query, as the comma would not be638* added to the query string.639**/640641if (first == false || flag == false) {642updateExec += ", ";643}644updateExec += crs.getMetaData().getColumnName(i);645cols.add(i);646updateExec += " = ? ";647first = false;648649/** Adding the extra condition for orig to be "not" null as the650* condition for orig to be null is take prior to this, if this651* is not added it will result in a NullPointerException when652* the values are compared.653**/654655} else if (orig.equals(curr)) {656colsNotChanged++;657//nothing to update in this case since values are equal658659/** Adding the extra condition for orig to be "not" null as the660* condition for orig to be null is take prior to this, if this661* is not added it will result in a NullPointerException when662* the values are compared.663**/664665} else if(orig.equals(curr) == false) {666// When values from db and values in CachedRowSet are not equal,667// if db value is same as before updation for each col in668// the row before fetching into CachedRowSet,669// only then we go ahead with updation, else we670// throw SyncProviderException.671672// if value has changed in db after fetching from db673// for some cols of the row and at the same time, some other cols674// have changed in CachedRowSet, no synchronization happens675676// Synchronization happens only when data when fetching is677// same or at most has changed in cachedrowset678679// check orig value with what is there in crs for a column680// before updation in crs.681682if(crs.columnUpdated(i)) {683if(rsval.equals(orig)) {684// At this point we are sure that685// the value updated in crs was from686// what is in db now and has not changed687if (flag == false || first == false) {688updateExec += ", ";689}690updateExec += crs.getMetaData().getColumnName(i);691cols.add(i);692updateExec += " = ? ";693flag = false;694} else {695// Here the value has changed in the db after696// data was fetched697// Plus store this row from CachedRowSet and keep it698// in a new CachedRowSet699boolNull= false;700objVal = rsval;701iChangedValsInDbAndCRS++;702}703}704}705706if(!boolNull) {707this.crsResolve.updateObject(i,objVal);708} else {709this.crsResolve.updateNull(i);710}711} //end for712713rs.close();714pstmt.close();715716this.crsResolve.insertRow();717this.crsResolve.moveToCurrentRow();718719/**720* if nothing has changed return now - this can happen721* if column is updated to the same value.722* if colsNotChanged == callerColumnCount implies we are updating723* the database with ALL COLUMNS HAVING SAME VALUES,724* so skip going to database, else do as usual.725**/726if ( (first == false && cols.size() == 0) ||727colsNotChanged == callerColumnCount ) {728return false;729}730731if(iChangedValsInDbAndCRS != 0 || iChangedValsinDbOnly != 0) {732return true;733}734735736updateExec += updateWhere;737738pstmt = con.prepareStatement(updateExec);739740// Comments needed here741for (i = 0; i < cols.size(); i++) {742Object obj = crs.getObject(cols.get(i));743if (obj != null)744pstmt.setObject(i + 1, obj);745else746pstmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));747}748idx = i;749750// Comments needed here751for (i = 0; i < keyCols.length; i++) {752if (params[i] != null) {753pstmt.setObject(++idx, params[i]);754} else {755continue;756}757}758759i = pstmt.executeUpdate();760761/**762* i should be equal to 1(row count), because we update763* one row(returned as row count) at a time, if all goes well.764* if 1 != 1, this implies we have not been able to765* do updations properly i.e there is a conflict in database766* versus what is in CachedRowSet for this particular row.767**/768769return false;770771} else {772/**773* Cursor will be here, if the ResultSet may not return even a single row774* i.e. we can't find the row where to update because it has been deleted775* etc. from the db.776* Present the whole row as null to user, to force null to be sync'ed777* and hence nothing to be synced.778*779* NOTE:780* ------781* In the database if a column that is mapped to java.sql.Types.REAL stores782* a Double value and is compared with value got from ResultSet.getFloat()783* no row is retrieved and will throw a SyncProviderException. For details784* see bug Id 5053830785**/786return true;787}788} catch (SQLException ex) {789ex.printStackTrace();790// if executeUpdate fails it will come here,791// update crsResolve with null rows792this.crsResolve.moveToInsertRow();793794for(i = 1; i <= callerColumnCount; i++) {795this.crsResolve.updateNull(i);796}797798this.crsResolve.insertRow();799this.crsResolve.moveToCurrentRow();800801return true;802}803}804805/**806* Inserts a row that has been inserted into the given807* <code>CachedRowSet</code> object into the data source from which808* the rowset is derived, returning <code>false</code> if the insertion809* was successful.810*811* @param crs the <code>CachedRowSet</code> object that has had a row inserted812* and to whose underlying data source the row will be inserted813* @param pstmt the <code>PreparedStatement</code> object that will be used814* to execute the insertion815* @return <code>false</code> to indicate that the insertion was successful;816* <code>true</code> otherwise817* @throws SQLException if a database access error occurs818*/819private boolean insertNewRow(CachedRowSet crs,820PreparedStatement pstmt, CachedRowSetImpl crsRes) throws SQLException {821822boolean returnVal = false;823824try (PreparedStatement pstmtSel = con.prepareStatement(selectCmd,825ResultSet.TYPE_SCROLL_SENSITIVE,826ResultSet.CONCUR_READ_ONLY);827ResultSet rs = pstmtSel.executeQuery();828ResultSet rs2 = con.getMetaData().getPrimaryKeys(null, null,829crs.getTableName())830) {831832ResultSetMetaData rsmd = crs.getMetaData();833int icolCount = rsmd.getColumnCount();834String[] primaryKeys = new String[icolCount];835int k = 0;836while (rs2.next()) {837primaryKeys[k] = rs2.getString("COLUMN_NAME");838k++;839}840841if (rs.next()) {842for (String pkName : primaryKeys) {843if (!isPKNameValid(pkName, rsmd)) {844845/* We came here as one of the the primary keys846* of the table is not present in the cached847* rowset object, it should be an autoincrement column848* and not included while creating CachedRowSet849* Object, proceed to check for other primary keys850*/851continue;852}853854Object crsPK = crs.getObject(pkName);855if (crsPK == null) {856/*857* It is possible that the PK is null on some databases858* and will be filled in at insert time (MySQL for example)859*/860break;861}862863String rsPK = rs.getObject(pkName).toString();864if (crsPK.toString().equals(rsPK)) {865returnVal = true;866this.crsResolve.moveToInsertRow();867for (int i = 1; i <= icolCount; i++) {868String colname = (rs.getMetaData()).getColumnName(i);869if (colname.equals(pkName))870this.crsResolve.updateObject(i,rsPK);871else872this.crsResolve.updateNull(i);873}874this.crsResolve.insertRow();875this.crsResolve.moveToCurrentRow();876}877}878}879880if (returnVal) {881return returnVal;882}883884try {885for (int i = 1; i <= icolCount; i++) {886Object obj = crs.getObject(i);887if (obj != null) {888pstmt.setObject(i, obj);889} else {890pstmt.setNull(i,crs.getMetaData().getColumnType(i));891}892}893894pstmt.executeUpdate();895return false;896897} catch (SQLException ex) {898/*899* Cursor will come here if executeUpdate fails.900* There can be many reasons why the insertion failed,901* one can be violation of primary key.902* Hence we cannot exactly identify why the insertion failed,903* present the current row as a null row to the caller.904*/905this.crsResolve.moveToInsertRow();906907for (int i = 1; i <= icolCount; i++) {908this.crsResolve.updateNull(i);909}910911this.crsResolve.insertRow();912this.crsResolve.moveToCurrentRow();913914return true;915}916}917}918919/**920* Deletes the row in the underlying data source that corresponds to921* a row that has been deleted in the given <code> CachedRowSet</code> object922* and returns <code>false</code> if the deletion was successful.923* <P>924* This method is called internally by this writer's <code>writeData</code>925* method when a row in the rowset has been deleted. The values in the926* deleted row are the same as those that are stored in the original row927* of the given <code>CachedRowSet</code> object. If the values in the928* original row differ from the row in the underlying data source, the row929* in the data source is not deleted, and <code>deleteOriginalRow</code>930* returns <code>true</code> to indicate that there was a conflict.931*932*933* @return <code>false</code> if the deletion was successful, which means that934* there was no conflict; <code>true</code> otherwise935* @throws SQLException if there was a database access error936*/937private boolean deleteOriginalRow(CachedRowSet crs, CachedRowSetImpl crsRes) throws SQLException {938PreparedStatement pstmt;939int i;940int idx = 0;941String strSelect;942// Select the row from the database.943ResultSet origVals = crs.getOriginalRow();944origVals.next();945946deleteWhere = buildWhereClause(deleteWhere, origVals);947pstmt = con.prepareStatement(selectCmd + deleteWhere,948ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);949950for (i = 0; i < keyCols.length; i++) {951if (params[i] != null) {952pstmt.setObject(++idx, params[i]);953} else {954continue;955}956}957958try {959pstmt.setMaxRows(crs.getMaxRows());960pstmt.setMaxFieldSize(crs.getMaxFieldSize());961pstmt.setEscapeProcessing(crs.getEscapeProcessing());962pstmt.setQueryTimeout(crs.getQueryTimeout());963} catch (Exception ex) {964/*965* Older driver don't support these operations...966*/967;968}969970ResultSet rs = pstmt.executeQuery();971972if (rs.next() == true) {973if (rs.next()) {974// more than one row975return true;976}977rs.first();978979// Now check all the values in rs to be same in980// db also before actually going ahead with deleting981boolean boolChanged = false;982983crsRes.moveToInsertRow();984985for (i = 1; i <= crs.getMetaData().getColumnCount(); i++) {986987Object original = origVals.getObject(i);988Object changed = rs.getObject(i);989990if(original != null && changed != null ) {991if(! (original.toString()).equals(changed.toString()) ) {992boolChanged = true;993crsRes.updateObject(i,origVals.getObject(i));994}995} else {996crsRes.updateNull(i);997}998}9991000crsRes.insertRow();1001crsRes.moveToCurrentRow();10021003if(boolChanged) {1004// do not delete as values in db have changed1005// deletion will not happen for this row from db1006// exit now returning true. i.e. conflict1007return true;1008} else {1009// delete the row.1010// Go ahead with deleting,1011// don't do anything here1012}10131014String cmd = deleteCmd + deleteWhere;1015pstmt = con.prepareStatement(cmd);10161017idx = 0;1018for (i = 0; i < keyCols.length; i++) {1019if (params[i] != null) {1020pstmt.setObject(++idx, params[i]);1021} else {1022continue;1023}1024}10251026if (pstmt.executeUpdate() != 1) {1027return true;1028}1029pstmt.close();1030} else {1031// didn't find the row1032return true;1033}10341035// no conflict1036return false;1037}10381039/**1040* Sets the reader for this writer to the given reader.1041*1042* @throws SQLException if a database access error occurs1043*/1044public void setReader(CachedRowSetReader reader) throws SQLException {1045this.reader = reader;1046}10471048/**1049* Gets the reader for this writer.1050*1051* @throws SQLException if a database access error occurs1052*/1053public CachedRowSetReader getReader() throws SQLException {1054return reader;1055}10561057/**1058* Composes a <code>SELECT</code>, <code>UPDATE</code>, <code>INSERT</code>,1059* and <code>DELETE</code> statement that can be used by this writer to1060* write data to the data source backing the given <code>CachedRowSet</code>1061* object.1062*1063* @ param caller a <code>CachedRowSet</code> object for which this1064* <code>CachedRowSetWriter</code> object is the writer1065* @throws SQLException if a database access error occurs1066*/1067private void initSQLStatements(CachedRowSet caller) throws SQLException {10681069int i;10701071callerMd = caller.getMetaData();1072callerColumnCount = callerMd.getColumnCount();1073if (callerColumnCount < 1)1074// No data, so return.1075return;10761077/*1078* If the RowSet has a Table name we should use it.1079* This is really a hack to get round the fact that1080* a lot of the jdbc drivers can't provide the tab.1081*/1082String table = caller.getTableName();1083if (table == null) {1084/*1085* attempt to build a table name using the info1086* that the driver gave us for the first column1087* in the source result set.1088*/1089table = callerMd.getTableName(1);1090if (table == null || table.length() == 0) {1091throw new SQLException(resBundle.handleGetObject("crswriter.tname").toString());1092}1093}1094String catalog = callerMd.getCatalogName(1);1095String schema = callerMd.getSchemaName(1);1096DatabaseMetaData dbmd = con.getMetaData();10971098/*1099* Compose a SELECT statement. There are three parts.1100*/11011102// Project List1103selectCmd = "SELECT ";1104for (i=1; i <= callerColumnCount; i++) {1105selectCmd += callerMd.getColumnName(i);1106if ( i < callerMd.getColumnCount() )1107selectCmd += ", ";1108else1109selectCmd += " ";1110}11111112// FROM clause.1113selectCmd += "FROM " + buildTableName(dbmd, catalog, schema, table);11141115/*1116* Compose an UPDATE statement.1117*/1118updateCmd = "UPDATE " + buildTableName(dbmd, catalog, schema, table);111911201121/**1122* The following block of code is for checking a particular type of1123* query where in there is a where clause. Without this block, if a1124* SQL statement is built the "where" clause will appear twice hence1125* the DB errors out and a SQLException is thrown. This code also1126* considers that the where clause is in the right place as the1127* CachedRowSet object would already have been populated with this1128* query before coming to this point.1129**/11301131String tempupdCmd = updateCmd.toLowerCase();11321133int idxupWhere = tempupdCmd.indexOf("where");11341135if(idxupWhere != -1)1136{1137updateCmd = updateCmd.substring(0,idxupWhere);1138}1139updateCmd += "SET ";11401141/*1142* Compose an INSERT statement.1143*/1144insertCmd = "INSERT INTO " + buildTableName(dbmd, catalog, schema, table);1145// Column list1146insertCmd += "(";1147for (i=1; i <= callerColumnCount; i++) {1148insertCmd += callerMd.getColumnName(i);1149if ( i < callerMd.getColumnCount() )1150insertCmd += ", ";1151else1152insertCmd += ") VALUES (";1153}1154for (i=1; i <= callerColumnCount; i++) {1155insertCmd += "?";1156if (i < callerColumnCount)1157insertCmd += ", ";1158else1159insertCmd += ")";1160}11611162/*1163* Compose a DELETE statement.1164*/1165deleteCmd = "DELETE FROM " + buildTableName(dbmd, catalog, schema, table);11661167/*1168* set the key desriptors that will be1169* needed to construct where clauses.1170*/1171buildKeyDesc(caller);1172}11731174/**1175* Returns a fully qualified table name built from the given catalog and1176* table names. The given metadata object is used to get the proper order1177* and separator.1178*1179* @param dbmd a <code>DatabaseMetaData</code> object that contains metadata1180* about this writer's <code>CachedRowSet</code> object1181* @param catalog a <code>String</code> object with the rowset's catalog1182* name1183* @param table a <code>String</code> object with the name of the table from1184* which this writer's rowset was derived1185* @return a <code>String</code> object with the fully qualified name of the1186* table from which this writer's rowset was derived1187* @throws SQLException if a database access error occurs1188*/1189private String buildTableName(DatabaseMetaData dbmd,1190String catalog, String schema, String table) throws SQLException {11911192// trim all the leading and trailing whitespaces,1193// white spaces can never be catalog, schema or a table name.11941195String cmd = "";11961197catalog = catalog.trim();1198schema = schema.trim();1199table = table.trim();12001201if (dbmd.isCatalogAtStart() == true) {1202if (catalog != null && catalog.length() > 0) {1203cmd += catalog + dbmd.getCatalogSeparator();1204}1205if (schema != null && schema.length() > 0) {1206cmd += schema + ".";1207}1208cmd += table;1209} else {1210if (schema != null && schema.length() > 0) {1211cmd += schema + ".";1212}1213cmd += table;1214if (catalog != null && catalog.length() > 0) {1215cmd += dbmd.getCatalogSeparator() + catalog;1216}1217}1218cmd += " ";1219return cmd;1220}12211222/**1223* Assigns to the given <code>CachedRowSet</code> object's1224* <code>params</code>1225* field an array whose length equals the number of columns needed1226* to uniquely identify a row in the rowset. The array is given1227* values by the method <code>buildWhereClause</code>.1228* <P>1229* If the <code>CachedRowSet</code> object's <code>keyCols</code>1230* field has length <code>0</code> or is <code>null</code>, the array1231* is set with the column number of every column in the rowset.1232* Otherwise, the array in the field <code>keyCols</code> is set with only1233* the column numbers of the columns that are required to form a unique1234* identifier for a row.1235*1236* @param crs the <code>CachedRowSet</code> object for which this1237* <code>CachedRowSetWriter</code> object is the writer1238*1239* @throws SQLException if a database access error occurs1240*/1241private void buildKeyDesc(CachedRowSet crs) throws SQLException {12421243keyCols = crs.getKeyColumns();1244ResultSetMetaData resultsetmd = crs.getMetaData();1245if (keyCols == null || keyCols.length == 0) {1246ArrayList<Integer> listKeys = new ArrayList<Integer>();12471248for (int i = 0; i < callerColumnCount; i++ ) {1249if(resultsetmd.getColumnType(i+1) != java.sql.Types.CLOB &&1250resultsetmd.getColumnType(i+1) != java.sql.Types.STRUCT &&1251resultsetmd.getColumnType(i+1) != java.sql.Types.SQLXML &&1252resultsetmd.getColumnType(i+1) != java.sql.Types.BLOB &&1253resultsetmd.getColumnType(i+1) != java.sql.Types.ARRAY &&1254resultsetmd.getColumnType(i+1) != java.sql.Types.OTHER )1255listKeys.add(i+1);1256}1257keyCols = new int[listKeys.size()];1258for (int i = 0; i < listKeys.size(); i++ )1259keyCols[i] = listKeys.get(i);1260}1261params = new Object[keyCols.length];1262}12631264/**1265* Constructs an SQL <code>WHERE</code> clause using the given1266* string as a starting point. The resulting clause will contain1267* a column name and " = ?" for each key column, that is, each column1268* that is needed to form a unique identifier for a row in the rowset.1269* This <code>WHERE</code> clause can be added to1270* a <code>PreparedStatement</code> object that updates, inserts, or1271* deletes a row.1272* <P>1273* This method uses the given result set to access values in the1274* <code>CachedRowSet</code> object that called this writer. These1275* values are used to build the array of parameters that will serve as1276* replacements for the "?" parameter placeholders in the1277* <code>PreparedStatement</code> object that is sent to the1278* <code>CachedRowSet</code> object's underlying data source.1279*1280* @param whereClause a <code>String</code> object that is an empty1281* string ("")1282* @param rs a <code>ResultSet</code> object that can be used1283* to access the <code>CachedRowSet</code> object's data1284* @return a <code>WHERE</code> clause of the form "<code>WHERE</code>1285* columnName = ? AND columnName = ? AND columnName = ? ..."1286* @throws SQLException if a database access error occurs1287*/1288private String buildWhereClause(String whereClause,1289ResultSet rs) throws SQLException {1290whereClause = "WHERE ";12911292for (int i = 0; i < keyCols.length; i++) {1293if (i > 0) {1294whereClause += "AND ";1295}1296whereClause += callerMd.getColumnName(keyCols[i]);1297params[i] = rs.getObject(keyCols[i]);1298if (rs.wasNull() == true) {1299whereClause += " IS NULL ";1300} else {1301whereClause += " = ? ";1302}1303}1304return whereClause;1305}13061307void updateResolvedConflictToDB(CachedRowSet crs, Connection con) throws SQLException {1308//String updateExe = ;1309PreparedStatement pStmt ;1310String strWhere = "WHERE " ;1311String strExec =" ";1312String strUpdate = "UPDATE ";1313int icolCount = crs.getMetaData().getColumnCount();1314int keyColumns[] = crs.getKeyColumns();1315Object param[];1316String strSet="";13171318strWhere = buildWhereClause(strWhere, crs);13191320if (keyColumns == null || keyColumns.length == 0) {1321keyColumns = new int[icolCount];1322for (int i = 0; i < keyColumns.length; ) {1323keyColumns[i] = ++i;1324}1325}1326param = new Object[keyColumns.length];13271328strUpdate = "UPDATE " + buildTableName(con.getMetaData(),1329crs.getMetaData().getCatalogName(1),1330crs.getMetaData().getSchemaName(1),1331crs.getTableName());13321333// changed or updated values will become part of1334// set clause here1335strUpdate += "SET ";13361337boolean first = true;13381339for (int i=1; i<=icolCount;i++) {1340if (crs.columnUpdated(i)) {1341if (first == false) {1342strSet += ", ";1343}1344strSet += crs.getMetaData().getColumnName(i);1345strSet += " = ? ";1346first = false;1347} //end if1348} //end for13491350// keycols will become part of where clause1351strUpdate += strSet;1352strWhere = "WHERE ";13531354for (int i = 0; i < keyColumns.length; i++) {1355if (i > 0) {1356strWhere += "AND ";1357}1358strWhere += crs.getMetaData().getColumnName(keyColumns[i]);1359param[i] = crs.getObject(keyColumns[i]);1360if (crs.wasNull() == true) {1361strWhere += " IS NULL ";1362} else {1363strWhere += " = ? ";1364}1365}1366strUpdate += strWhere;13671368pStmt = con.prepareStatement(strUpdate);13691370int idx =0;1371for (int i = 0; i < icolCount; i++) {1372if(crs.columnUpdated(i+1)) {1373Object obj = crs.getObject(i+1);1374if (obj != null) {1375pStmt.setObject(++idx, obj);1376} else {1377pStmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));1378} //end if ..else1379} //end if crs.column...1380} //end for13811382// Set the key cols for after WHERE =? clause1383for (int i = 0; i < keyColumns.length; i++) {1384if (param[i] != null) {1385pStmt.setObject(++idx, param[i]);1386}1387}13881389int id = pStmt.executeUpdate();1390}139113921393/**1394*1395*/1396public void commit() throws SQLException {1397con.commit();1398if (reader.getCloseConnection() == true) {1399con.close();1400}1401}14021403public void commit(CachedRowSetImpl crs, boolean updateRowset) throws SQLException {1404con.commit();1405if(updateRowset) {1406if(crs.getCommand() != null)1407crs.execute(con);1408}14091410if (reader.getCloseConnection() == true) {1411con.close();1412}1413}14141415/**1416*1417*/1418public void rollback() throws SQLException {1419con.rollback();1420if (reader.getCloseConnection() == true) {1421con.close();1422}1423}14241425/**1426*1427*/1428public void rollback(Savepoint s) throws SQLException {1429con.rollback(s);1430if (reader.getCloseConnection() == true) {1431con.close();1432}1433}14341435private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {1436// Default state initialization happens here1437ois.defaultReadObject();1438// Initialization of Res Bundle happens here .1439try {1440resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();1441} catch(IOException ioe) {1442throw new RuntimeException(ioe);1443}14441445}14461447static final long serialVersionUID =-8506030970299413976L;14481449/**1450* Validate whether the Primary Key is known to the CachedRowSet. If it is1451* not, it is an auto-generated key1452* @param pk - Primary Key to validate1453* @param rsmd - ResultSetMetadata for the RowSet1454* @return true if found, false otherwise (auto generated key)1455*/1456private boolean isPKNameValid(String pk, ResultSetMetaData rsmd) throws SQLException {1457boolean isValid = false;1458int cols = rsmd.getColumnCount();1459for(int i = 1; i<= cols; i++) {1460String colName = rsmd.getColumnClassName(i);1461if(colName.equalsIgnoreCase(pk)) {1462isValid = true;1463break;1464}1465}14661467return isValid;1468}1469}147014711472