Path: blob/master/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java
40948 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} implementation to46* propagate changes back to the data source from which the rowset got its data.47* <P>48* A {@code CachedRowSetWriter} object, called a writer, has the public49* method {@code writeData} 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} 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}.56* <P>57* Typically the {@code SyncFactory} manages the {@code RowSetReader} and58* the {@code RowSetWriter} implementations using {@code SyncProvider} objects.59* Standard JDBC RowSet implementations provide an object instance of this60* writer by invoking the {@code SyncProvider.getRowSetWriter()} 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} 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} command that this writer will call79* internally. The method {@code initSQLStatements} 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} command that this writer will call89* internally to write data to the rowset's underlying data source.90* The method {@code initSQLStatements} builds this {@code String}91* object.92*93* @serial94*/95private String updateCmd;9697/**98* The SQL {@code WHERE} clause the writer will use for update99* statements in the {@code PreparedStatement} object100* it sends to the underlying data source.101*102* @serial103*/104private String updateWhere;105106/**107* The SQL {@code DELETE} 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} clause the writer will use for delete116* statements in the {@code PreparedStatement} object117* it sends to the underlying data source.118*119* @serial120*/121private String deleteWhere;122123/**124* The SQL {@code INSERT INTO} command that this writer will internally use125* to insert data into the rowset's underlying data source. The method126* {@code initSQLStatements} 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} object136* for which this {@code CachedRowSetWriter} 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} object that this145* writer will execute.146*147* @serial148*/149private Object[] params;150151/**152* The {@code CachedRowSetReader} object that has been153* set as the reader for the {@code CachedRowSet} object154* for which this {@code CachedRowSetWriter} object is the writer.155*156* @serial157*/158private CachedRowSetReader reader;159160/**161* The {@code ResultSetMetaData} object that contains information162* about the columns in the {@code CachedRowSet} object163* for which this {@code CachedRowSetWriter} object is the writer.164*165* @serial166*/167private ResultSetMetaData callerMd;168169/**170* The number of columns in the {@code CachedRowSet} object171* for which this {@code CachedRowSetWriter} object is the writer.172*173* @serial174*/175private int callerColumnCount;176177/**178* This {@code CachedRowSet} will hold the conflicting values179* retrieved from the db and hold it.180*/181private CachedRowSetImpl crsResolve;182183/**184* This {@code ArrayList} 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} object212* back to its underlying data source and returns {@code true}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}.224* The {@code writeData} method in turn calls private methods that225* it defines internally.226* The following is a general summary of what the method227* {@code writeData} does, much of which is accomplished228* through calls to its own internal methods.229* <OL>230* <LI>Creates a {@code CachedRowSet} object from the given231* {@code RowSet} 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} to initialize new SQL statements240* <UL>241* <LI>Builds new {@code SELECT}, {@code UPDATE},242* {@code INSERT}, and {@code DELETE} statements243* <LI>Uses the {@code CachedRowSet} 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} object back to its underlying data source249* <UL>250* <LI>Iterates through each row of the {@code CachedRowSet} 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} 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} if changes to the rowset were successfully263* written to the rowset's underlying data source;264* {@code false} 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} object's underlying data438* source so that updates to the rowset are reflected in the original439* data source, and returns {@code false} if the update was successful.440* A return value of {@code true} 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}448* if a row in the {@code CachedRowSet} object for which this449* {@code CachedRowSetWriter} object is the writer has been updated.450*451* @return {@code false} if the update to the underlying data source is452* successful; {@code true} 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 {575ReflectUtil.checkPackageAccess(c);576@SuppressWarnings("deprecation")577Object tmp = c.newInstance();578obj = (SQLData)tmp;579} catch (Exception ex) {580throw new SQLException("Unable to Instantiate: ", ex);581}582// get the attributes from the struct583Object attribs[] = s.getAttributes(map);584// create the SQLInput "stream"585SQLInputImpl sqlInput = new SQLInputImpl(attribs, map);586// read the values...587obj.readSQL(sqlInput, s.getSQLTypeName());588rsval = obj;589}590} else if (rsval instanceof SQLData) {591rsval = new SerialStruct((SQLData)rsval, map);592} else if (rsval instanceof Blob) {593rsval = new SerialBlob((Blob)rsval);594} else if (rsval instanceof Clob) {595rsval = new SerialClob((Clob)rsval);596} else if (rsval instanceof java.sql.Array) {597rsval = new SerialArray((java.sql.Array)rsval, map);598}599600// reset boolNull if it had been set601boolNull = true;602603/** This addtional checking has been added when the current value604* in the DB is null, but the DB had a different value when the605* data was actaully fetched into the CachedRowSet.606**/607608if(rsval == null && orig != null) {609// value in db has changed610// don't proceed with synchronization611// get the value in db and pass it to the resolver.612613iChangedValsinDbOnly++;614// Set the boolNull to false,615// in order to set the actual value;616boolNull = false;617objVal = rsval;618}619620/** Adding the checking for rsval to be "not" null or else621* it would through a NullPointerException when the values622* are compared.623**/624625else if(rsval != null && (!rsval.equals(orig)))626{627// value in db has changed628// don't proceed with synchronization629// get the value in db and pass it to the resolver.630631iChangedValsinDbOnly++;632// Set the boolNull to false,633// in order to set the actual value;634boolNull = false;635objVal = rsval;636} else if ( (orig == null || curr == null) ) {637638/** Adding the additonal condition of checking for "flag"639* boolean variable, which would otherwise result in640* building a invalid query, as the comma would not be641* added to the query string.642**/643644if (first == false || flag == false) {645updateExec += ", ";646}647updateExec += crs.getMetaData().getColumnName(i);648cols.add(i);649updateExec += " = ? ";650first = false;651652/** Adding the extra condition for orig to be "not" null as the653* condition for orig to be null is take prior to this, if this654* is not added it will result in a NullPointerException when655* the values are compared.656**/657658} else if (orig.equals(curr)) {659colsNotChanged++;660//nothing to update in this case since values are equal661662/** Adding the extra condition for orig to be "not" null as the663* condition for orig to be null is take prior to this, if this664* is not added it will result in a NullPointerException when665* the values are compared.666**/667668} else if(orig.equals(curr) == false) {669// When values from db and values in CachedRowSet are not equal,670// if db value is same as before updation for each col in671// the row before fetching into CachedRowSet,672// only then we go ahead with updation, else we673// throw SyncProviderException.674675// if value has changed in db after fetching from db676// for some cols of the row and at the same time, some other cols677// have changed in CachedRowSet, no synchronization happens678679// Synchronization happens only when data when fetching is680// same or at most has changed in cachedrowset681682// check orig value with what is there in crs for a column683// before updation in crs.684685if(crs.columnUpdated(i)) {686if(rsval.equals(orig)) {687// At this point we are sure that688// the value updated in crs was from689// what is in db now and has not changed690if (flag == false || first == false) {691updateExec += ", ";692}693updateExec += crs.getMetaData().getColumnName(i);694cols.add(i);695updateExec += " = ? ";696flag = false;697} else {698// Here the value has changed in the db after699// data was fetched700// Plus store this row from CachedRowSet and keep it701// in a new CachedRowSet702boolNull= false;703objVal = rsval;704iChangedValsInDbAndCRS++;705}706}707}708709if(!boolNull) {710this.crsResolve.updateObject(i,objVal);711} else {712this.crsResolve.updateNull(i);713}714} //end for715716rs.close();717pstmt.close();718719this.crsResolve.insertRow();720this.crsResolve.moveToCurrentRow();721722/**723* if nothing has changed return now - this can happen724* if column is updated to the same value.725* if colsNotChanged == callerColumnCount implies we are updating726* the database with ALL COLUMNS HAVING SAME VALUES,727* so skip going to database, else do as usual.728**/729if ( (first == false && cols.size() == 0) ||730colsNotChanged == callerColumnCount ) {731return false;732}733734if(iChangedValsInDbAndCRS != 0 || iChangedValsinDbOnly != 0) {735return true;736}737738739updateExec += updateWhere;740741pstmt = con.prepareStatement(updateExec);742743// Comments needed here744for (i = 0; i < cols.size(); i++) {745Object obj = crs.getObject(cols.get(i));746if (obj != null)747pstmt.setObject(i + 1, obj);748else749pstmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));750}751idx = i;752753// Comments needed here754for (i = 0; i < keyCols.length; i++) {755if (params[i] != null) {756pstmt.setObject(++idx, params[i]);757} else {758continue;759}760}761762i = pstmt.executeUpdate();763764/**765* i should be equal to 1(row count), because we update766* one row(returned as row count) at a time, if all goes well.767* if 1 != 1, this implies we have not been able to768* do updations properly i.e there is a conflict in database769* versus what is in CachedRowSet for this particular row.770**/771772return false;773774} else {775/**776* Cursor will be here, if the ResultSet may not return even a single row777* i.e. we can't find the row where to update because it has been deleted778* etc. from the db.779* Present the whole row as null to user, to force null to be sync'ed780* and hence nothing to be synced.781*782* NOTE:783* ------784* In the database if a column that is mapped to java.sql.Types.REAL stores785* a Double value and is compared with value got from ResultSet.getFloat()786* no row is retrieved and will throw a SyncProviderException. For details787* see bug Id 5053830788**/789return true;790}791} catch (SQLException ex) {792ex.printStackTrace();793// if executeUpdate fails it will come here,794// update crsResolve with null rows795this.crsResolve.moveToInsertRow();796797for(i = 1; i <= callerColumnCount; i++) {798this.crsResolve.updateNull(i);799}800801this.crsResolve.insertRow();802this.crsResolve.moveToCurrentRow();803804return true;805}806}807808/**809* Inserts a row that has been inserted into the given810* {@code CachedRowSet} object into the data source from which811* the rowset is derived, returning {@code false} if the insertion812* was successful.813*814* @param crs the {@code CachedRowSet} object that has had a row inserted815* and to whose underlying data source the row will be inserted816* @param pstmt the {@code PreparedStatement} object that will be used817* to execute the insertion818* @return {@code false} to indicate that the insertion was successful;819* {@code true} otherwise820* @throws SQLException if a database access error occurs821*/822private boolean insertNewRow(CachedRowSet crs,823PreparedStatement pstmt, CachedRowSetImpl crsRes) throws SQLException {824825boolean returnVal = false;826827try (PreparedStatement pstmtSel = con.prepareStatement(selectCmd,828ResultSet.TYPE_SCROLL_SENSITIVE,829ResultSet.CONCUR_READ_ONLY);830ResultSet rs = pstmtSel.executeQuery();831ResultSet rs2 = con.getMetaData().getPrimaryKeys(null, null,832crs.getTableName())833) {834835ResultSetMetaData rsmd = crs.getMetaData();836int icolCount = rsmd.getColumnCount();837String[] primaryKeys = new String[icolCount];838int k = 0;839while (rs2.next()) {840primaryKeys[k] = rs2.getString("COLUMN_NAME");841k++;842}843844if (rs.next()) {845for (String pkName : primaryKeys) {846if (!isPKNameValid(pkName, rsmd)) {847848/* We came here as one of the primary keys849* of the table is not present in the cached850* rowset object, it should be an autoincrement column851* and not included while creating CachedRowSet852* Object, proceed to check for other primary keys853*/854continue;855}856857Object crsPK = crs.getObject(pkName);858if (crsPK == null) {859/*860* It is possible that the PK is null on some databases861* and will be filled in at insert time (MySQL for example)862*/863break;864}865866String rsPK = rs.getObject(pkName).toString();867if (crsPK.toString().equals(rsPK)) {868returnVal = true;869this.crsResolve.moveToInsertRow();870for (int i = 1; i <= icolCount; i++) {871String colname = (rs.getMetaData()).getColumnName(i);872if (colname.equals(pkName))873this.crsResolve.updateObject(i,rsPK);874else875this.crsResolve.updateNull(i);876}877this.crsResolve.insertRow();878this.crsResolve.moveToCurrentRow();879}880}881}882883if (returnVal) {884return returnVal;885}886887try {888for (int i = 1; i <= icolCount; i++) {889Object obj = crs.getObject(i);890if (obj != null) {891pstmt.setObject(i, obj);892} else {893pstmt.setNull(i,crs.getMetaData().getColumnType(i));894}895}896897pstmt.executeUpdate();898return false;899900} catch (SQLException ex) {901/*902* Cursor will come here if executeUpdate fails.903* There can be many reasons why the insertion failed,904* one can be violation of primary key.905* Hence we cannot exactly identify why the insertion failed,906* present the current row as a null row to the caller.907*/908this.crsResolve.moveToInsertRow();909910for (int i = 1; i <= icolCount; i++) {911this.crsResolve.updateNull(i);912}913914this.crsResolve.insertRow();915this.crsResolve.moveToCurrentRow();916917return true;918}919}920}921922/**923* Deletes the row in the underlying data source that corresponds to924* a row that has been deleted in the given {@code CachedRowSet} object925* and returns {@code false} if the deletion was successful.926* <P>927* This method is called internally by this writer's {@code writeData}928* method when a row in the rowset has been deleted. The values in the929* deleted row are the same as those that are stored in the original row930* of the given {@code CachedRowSet} object. If the values in the931* original row differ from the row in the underlying data source, the row932* in the data source is not deleted, and {@code deleteOriginalRow}933* returns {@code true} to indicate that there was a conflict.934*935*936* @return {@code false} if the deletion was successful, which means that937* there was no conflict; {@code true} otherwise938* @throws SQLException if there was a database access error939*/940private boolean deleteOriginalRow(CachedRowSet crs, CachedRowSetImpl crsRes) throws SQLException {941PreparedStatement pstmt;942int i;943int idx = 0;944String strSelect;945// Select the row from the database.946ResultSet origVals = crs.getOriginalRow();947origVals.next();948949deleteWhere = buildWhereClause(deleteWhere, origVals);950pstmt = con.prepareStatement(selectCmd + deleteWhere,951ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);952953for (i = 0; i < keyCols.length; i++) {954if (params[i] != null) {955pstmt.setObject(++idx, params[i]);956} else {957continue;958}959}960961try {962pstmt.setMaxRows(crs.getMaxRows());963pstmt.setMaxFieldSize(crs.getMaxFieldSize());964pstmt.setEscapeProcessing(crs.getEscapeProcessing());965pstmt.setQueryTimeout(crs.getQueryTimeout());966} catch (Exception ex) {967/*968* Older driver don't support these operations...969*/970;971}972973ResultSet rs = pstmt.executeQuery();974975if (rs.next() == true) {976if (rs.next()) {977// more than one row978return true;979}980rs.first();981982// Now check all the values in rs to be same in983// db also before actually going ahead with deleting984boolean boolChanged = false;985986crsRes.moveToInsertRow();987988for (i = 1; i <= crs.getMetaData().getColumnCount(); i++) {989990Object original = origVals.getObject(i);991Object changed = rs.getObject(i);992993if(original != null && changed != null ) {994if(! (original.toString()).equals(changed.toString()) ) {995boolChanged = true;996crsRes.updateObject(i,origVals.getObject(i));997}998} else {999crsRes.updateNull(i);1000}1001}10021003crsRes.insertRow();1004crsRes.moveToCurrentRow();10051006if(boolChanged) {1007// do not delete as values in db have changed1008// deletion will not happen for this row from db1009// exit now returning true. i.e. conflict1010return true;1011} else {1012// delete the row.1013// Go ahead with deleting,1014// don't do anything here1015}10161017String cmd = deleteCmd + deleteWhere;1018pstmt = con.prepareStatement(cmd);10191020idx = 0;1021for (i = 0; i < keyCols.length; i++) {1022if (params[i] != null) {1023pstmt.setObject(++idx, params[i]);1024} else {1025continue;1026}1027}10281029if (pstmt.executeUpdate() != 1) {1030return true;1031}1032pstmt.close();1033} else {1034// didn't find the row1035return true;1036}10371038// no conflict1039return false;1040}10411042/**1043* Sets the reader for this writer to the given reader.1044*1045* @throws SQLException if a database access error occurs1046*/1047public void setReader(CachedRowSetReader reader) throws SQLException {1048this.reader = reader;1049}10501051/**1052* Gets the reader for this writer.1053*1054* @throws SQLException if a database access error occurs1055*/1056public CachedRowSetReader getReader() throws SQLException {1057return reader;1058}10591060/**1061* Composes a {@code SELECT}, {@code UPDATE}, {@code INSERT},1062* and {@code DELETE} statement that can be used by this writer to1063* write data to the data source backing the given {@code CachedRowSet}1064* object.1065*1066* @param caller a {@code CachedRowSet} object for which this1067* {@code CachedRowSetWriter} object is the writer1068* @throws SQLException if a database access error occurs1069*/1070private void initSQLStatements(CachedRowSet caller) throws SQLException {10711072int i;10731074callerMd = caller.getMetaData();1075callerColumnCount = callerMd.getColumnCount();1076if (callerColumnCount < 1)1077// No data, so return.1078return;10791080/*1081* If the RowSet has a Table name we should use it.1082* This is really a hack to get round the fact that1083* a lot of the jdbc drivers can't provide the tab.1084*/1085String table = caller.getTableName();1086if (table == null) {1087/*1088* attempt to build a table name using the info1089* that the driver gave us for the first column1090* in the source result set.1091*/1092table = callerMd.getTableName(1);1093if (table == null || table.length() == 0) {1094throw new SQLException(resBundle.handleGetObject("crswriter.tname").toString());1095}1096}1097String catalog = callerMd.getCatalogName(1);1098String schema = callerMd.getSchemaName(1);1099DatabaseMetaData dbmd = con.getMetaData();11001101/*1102* Compose a SELECT statement. There are three parts.1103*/11041105// Project List1106selectCmd = "SELECT ";1107for (i=1; i <= callerColumnCount; i++) {1108selectCmd += callerMd.getColumnName(i);1109if ( i < callerMd.getColumnCount() )1110selectCmd += ", ";1111else1112selectCmd += " ";1113}11141115// FROM clause.1116selectCmd += "FROM " + buildTableName(dbmd, catalog, schema, table);11171118/*1119* Compose an UPDATE statement.1120*/1121updateCmd = "UPDATE " + buildTableName(dbmd, catalog, schema, table);112211231124/**1125* The following block of code is for checking a particular type of1126* query where in there is a where clause. Without this block, if a1127* SQL statement is built the "where" clause will appear twice hence1128* the DB errors out and a SQLException is thrown. This code also1129* considers that the where clause is in the right place as the1130* CachedRowSet object would already have been populated with this1131* query before coming to this point.1132**/11331134String tempupdCmd = updateCmd.toLowerCase();11351136int idxupWhere = tempupdCmd.indexOf("where");11371138if(idxupWhere != -1)1139{1140updateCmd = updateCmd.substring(0,idxupWhere);1141}1142updateCmd += "SET ";11431144/*1145* Compose an INSERT statement.1146*/1147insertCmd = "INSERT INTO " + buildTableName(dbmd, catalog, schema, table);1148// Column list1149insertCmd += "(";1150for (i=1; i <= callerColumnCount; i++) {1151insertCmd += callerMd.getColumnName(i);1152if ( i < callerMd.getColumnCount() )1153insertCmd += ", ";1154else1155insertCmd += ") VALUES (";1156}1157for (i=1; i <= callerColumnCount; i++) {1158insertCmd += "?";1159if (i < callerColumnCount)1160insertCmd += ", ";1161else1162insertCmd += ")";1163}11641165/*1166* Compose a DELETE statement.1167*/1168deleteCmd = "DELETE FROM " + buildTableName(dbmd, catalog, schema, table);11691170/*1171* set the key desriptors that will be1172* needed to construct where clauses.1173*/1174buildKeyDesc(caller);1175}11761177/**1178* Returns a fully qualified table name built from the given catalog and1179* table names. The given metadata object is used to get the proper order1180* and separator.1181*1182* @param dbmd a {@code DatabaseMetaData} object that contains metadata1183* about this writer's {@code CachedRowSet} object1184* @param catalog a {@code String} object with the rowset's catalog1185* name1186* @param table a {@code String} object with the name of the table from1187* which this writer's rowset was derived1188* @return a {@code String} object with the fully qualified name of the1189* table from which this writer's rowset was derived1190* @throws SQLException if a database access error occurs1191*/1192private String buildTableName(DatabaseMetaData dbmd,1193String catalog, String schema, String table) throws SQLException {11941195// trim all the leading and trailing whitespaces,1196// white spaces can never be catalog, schema or a table name.11971198String cmd = "";11991200catalog = catalog.trim();1201schema = schema.trim();1202table = table.trim();12031204if (dbmd.isCatalogAtStart() == true) {1205if (catalog != null && catalog.length() > 0) {1206cmd += catalog + dbmd.getCatalogSeparator();1207}1208if (schema != null && schema.length() > 0) {1209cmd += schema + ".";1210}1211cmd += table;1212} else {1213if (schema != null && schema.length() > 0) {1214cmd += schema + ".";1215}1216cmd += table;1217if (catalog != null && catalog.length() > 0) {1218cmd += dbmd.getCatalogSeparator() + catalog;1219}1220}1221cmd += " ";1222return cmd;1223}12241225/**1226* Assigns to the given {@code CachedRowSet} object's1227* {@code params}1228* field an array whose length equals the number of columns needed1229* to uniquely identify a row in the rowset. The array is given1230* values by the method {@code buildWhereClause}.1231* <P>1232* If the {@code CachedRowSet} object's {@code keyCols}1233* field has length {@code 0} or is {@code null}, the array1234* is set with the column number of every column in the rowset.1235* Otherwise, the array in the field {@code keyCols} is set with only1236* the column numbers of the columns that are required to form a unique1237* identifier for a row.1238*1239* @param crs the {@code CachedRowSet} object for which this1240* {@code CachedRowSetWriter} object is the writer1241*1242* @throws SQLException if a database access error occurs1243*/1244private void buildKeyDesc(CachedRowSet crs) throws SQLException {12451246keyCols = crs.getKeyColumns();1247ResultSetMetaData resultsetmd = crs.getMetaData();1248if (keyCols == null || keyCols.length == 0) {1249ArrayList<Integer> listKeys = new ArrayList<Integer>();12501251for (int i = 0; i < callerColumnCount; i++ ) {1252if(resultsetmd.getColumnType(i+1) != java.sql.Types.CLOB &&1253resultsetmd.getColumnType(i+1) != java.sql.Types.STRUCT &&1254resultsetmd.getColumnType(i+1) != java.sql.Types.SQLXML &&1255resultsetmd.getColumnType(i+1) != java.sql.Types.BLOB &&1256resultsetmd.getColumnType(i+1) != java.sql.Types.ARRAY &&1257resultsetmd.getColumnType(i+1) != java.sql.Types.OTHER )1258listKeys.add(i+1);1259}1260keyCols = new int[listKeys.size()];1261for (int i = 0; i < listKeys.size(); i++ )1262keyCols[i] = listKeys.get(i);1263}1264params = new Object[keyCols.length];1265}12661267/**1268* Constructs an SQL {@code WHERE} clause using the given1269* string as a starting point. The resulting clause will contain1270* a column name and " = ?" for each key column, that is, each column1271* that is needed to form a unique identifier for a row in the rowset.1272* This {@code WHERE} clause can be added to1273* a {@code PreparedStatement} object that updates, inserts, or1274* deletes a row.1275* <P>1276* This method uses the given result set to access values in the1277* {@code CachedRowSet} object that called this writer. These1278* values are used to build the array of parameters that will serve as1279* replacements for the "?" parameter placeholders in the1280* {@code PreparedStatement} object that is sent to the1281* {@code CachedRowSet} object's underlying data source.1282*1283* @param whereClause a {@code String} object that is an empty1284* string ("")1285* @param rs a {@code ResultSet} object that can be used1286* to access the {@code CachedRowSet} object's data1287* @return a {@code WHERE} clause of the form "{@code WHERE}1288* columnName = ? AND columnName = ? AND columnName = ? ..."1289* @throws SQLException if a database access error occurs1290*/1291private String buildWhereClause(String whereClause,1292ResultSet rs) throws SQLException {1293whereClause = "WHERE ";12941295for (int i = 0; i < keyCols.length; i++) {1296if (i > 0) {1297whereClause += "AND ";1298}1299whereClause += callerMd.getColumnName(keyCols[i]);1300params[i] = rs.getObject(keyCols[i]);1301if (rs.wasNull() == true) {1302whereClause += " IS NULL ";1303} else {1304whereClause += " = ? ";1305}1306}1307return whereClause;1308}13091310void updateResolvedConflictToDB(CachedRowSet crs, Connection con) throws SQLException {1311//String updateExe = ;1312PreparedStatement pStmt ;1313String strWhere = "WHERE " ;1314String strExec =" ";1315String strUpdate = "UPDATE ";1316int icolCount = crs.getMetaData().getColumnCount();1317int keyColumns[] = crs.getKeyColumns();1318Object param[];1319String strSet="";13201321strWhere = buildWhereClause(strWhere, crs);13221323if (keyColumns == null || keyColumns.length == 0) {1324keyColumns = new int[icolCount];1325for (int i = 0; i < keyColumns.length; ) {1326keyColumns[i] = ++i;1327}1328}1329param = new Object[keyColumns.length];13301331strUpdate = "UPDATE " + buildTableName(con.getMetaData(),1332crs.getMetaData().getCatalogName(1),1333crs.getMetaData().getSchemaName(1),1334crs.getTableName());13351336// changed or updated values will become part of1337// set clause here1338strUpdate += "SET ";13391340boolean first = true;13411342for (int i=1; i<=icolCount;i++) {1343if (crs.columnUpdated(i)) {1344if (first == false) {1345strSet += ", ";1346}1347strSet += crs.getMetaData().getColumnName(i);1348strSet += " = ? ";1349first = false;1350} //end if1351} //end for13521353// keycols will become part of where clause1354strUpdate += strSet;1355strWhere = "WHERE ";13561357for (int i = 0; i < keyColumns.length; i++) {1358if (i > 0) {1359strWhere += "AND ";1360}1361strWhere += crs.getMetaData().getColumnName(keyColumns[i]);1362param[i] = crs.getObject(keyColumns[i]);1363if (crs.wasNull() == true) {1364strWhere += " IS NULL ";1365} else {1366strWhere += " = ? ";1367}1368}1369strUpdate += strWhere;13701371pStmt = con.prepareStatement(strUpdate);13721373int idx =0;1374for (int i = 0; i < icolCount; i++) {1375if(crs.columnUpdated(i+1)) {1376Object obj = crs.getObject(i+1);1377if (obj != null) {1378pStmt.setObject(++idx, obj);1379} else {1380pStmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));1381} //end if ..else1382} //end if crs.column...1383} //end for13841385// Set the key cols for after WHERE =? clause1386for (int i = 0; i < keyColumns.length; i++) {1387if (param[i] != null) {1388pStmt.setObject(++idx, param[i]);1389}1390}13911392int id = pStmt.executeUpdate();1393}139413951396/**1397*1398*/1399public void commit() throws SQLException {1400con.commit();1401if (reader.getCloseConnection() == true) {1402con.close();1403}1404}14051406public void commit(CachedRowSetImpl crs, boolean updateRowset) throws SQLException {1407con.commit();1408if(updateRowset) {1409if(crs.getCommand() != null)1410crs.execute(con);1411}14121413if (reader.getCloseConnection() == true) {1414con.close();1415}1416}14171418/**1419*1420*/1421public void rollback() throws SQLException {1422con.rollback();1423if (reader.getCloseConnection() == true) {1424con.close();1425}1426}14271428/**1429*1430*/1431public void rollback(Savepoint s) throws SQLException {1432con.rollback(s);1433if (reader.getCloseConnection() == true) {1434con.close();1435}1436}14371438private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {1439// Default state initialization happens here1440ois.defaultReadObject();1441// Initialization of Res Bundle happens here .1442try {1443resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();1444} catch(IOException ioe) {1445throw new RuntimeException(ioe);1446}14471448}14491450static final long serialVersionUID =-8506030970299413976L;14511452/**1453* Validate whether the Primary Key is known to the CachedRowSet. If it is1454* not, it is an auto-generated key1455* @param pk - Primary Key to validate1456* @param rsmd - ResultSetMetadata for the RowSet1457* @return true if found, false otherwise (auto generated key)1458*/1459private boolean isPKNameValid(String pk, ResultSetMetaData rsmd) throws SQLException {1460boolean isValid = false;1461int cols = rsmd.getColumnCount();1462for(int i = 1; i<= cols; i++) {1463String colName = rsmd.getColumnClassName(i);1464if(colName.equalsIgnoreCase(pk)) {1465isValid = true;1466break;1467}1468}14691470return isValid;1471}14721473}147414751476