History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/com/sun/rowset/internal/CachedRowSetWriter.java (1 of 18352 files). Show all

Unified · Split

java/pkg/com/sun/rowset/internal/CachedRowSetWriter.java+1440−0
@@ -0,0 +1,1440 @@
1/*
2 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.rowset.internal;
27
28import java.sql.*;
29import javax.sql.*;
30import java.util.*;
31import java.io.*;
32
33import 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.*;
42
43
44/**
45 * The facility called on internally by the <code>RIOptimisticProvider</code> implementation to
46 * 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 public
49 * 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 getting
52 * the <code>CachedRowSetReader</code> object, called a reader, that is associated
53 * with the writer. The remainder of the methods in this class are private and
54 * are invoked internally, either directly or indirectly, by the method
55 * <code>writeData</code>.
56 * <P>
57 * Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and
58 * the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects.
59 * Standard JDBC RowSet implementations provide an object instance of this
60 * writer by invoking the <code>SyncProvider.getRowSetWriter()</code> method.
61 *
62 * @version 0.2
63 * @author Jonathan Bruce
64 * @see javax.sql.rowset.spi.SyncProvider
65 * @see javax.sql.rowset.spi.SyncFactory
66 * @see javax.sql.rowset.spi.SyncFactoryException
67 */
68public class CachedRowSetWriter implements TransactionalWriter, Serializable {
69
70/**
71 * The <code>Connection</code> object that this writer will use to make a
72 * connection to the data source to which it will write data.
73 *
74 */
75 private transient Connection con;
76
77/**
78 * The SQL <code>SELECT</code> command that this writer will call
79 * internally. The method <code>initSQLStatements</code> builds this
80 * command by supplying the words "SELECT" and "FROM," and using
81 * metadata to get the table name and column names .
82 *
83 * @serial
84 */
85 private String selectCmd;
86
87/**
88 * The SQL <code>UPDATE</code> command that this writer will call
89 * 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 * @serial
94 */
95 private String updateCmd;
96
97/**
98 * The SQL <code>WHERE</code> clause the writer will use for update
99 * statements in the <code>PreparedStatement</code> object
100 * it sends to the underlying data source.
101 *
102 * @serial
103 */
104 private String updateWhere;
105
106/**
107 * The SQL <code>DELETE</code> command that this writer will call
108 * internally to delete a row in the rowset's underlying data source.
109 *
110 * @serial
111 */
112 private String deleteCmd;
113
114/**
115 * The SQL <code>WHERE</code> clause the writer will use for delete
116 * statements in the <code>PreparedStatement</code> object
117 * it sends to the underlying data source.
118 *
119 * @serial
120 */
121 private String deleteWhere;
122
123/**
124 * The SQL <code>INSERT INTO</code> command that this writer will internally use
125 * to insert data into the rowset's underlying data source. The method
126 * <code>initSQLStatements</code> builds this command with a question
127 * mark parameter placeholder for each column in the rowset.
128 *
129 * @serial
130 */
131 private String insertCmd;
132
133/**
134 * An array containing the column numbers of the columns that are
135 * needed to uniquely identify a row in the <code>CachedRowSet</code> object
136 * for which this <code>CachedRowSetWriter</code> object is the writer.
137 *
138 * @serial
139 */
140 private int[] keyCols;
141
142/**
143 * An array of the parameters that should be used to set the parameter
144 * placeholders in a <code>PreparedStatement</code> object that this
145 * writer will execute.
146 *
147 * @serial
148 */
149 private Object[] params;
150
151/**
152 * The <code>CachedRowSetReader</code> object that has been
153 * set as the reader for the <code>CachedRowSet</code> object
154 * for which this <code>CachedRowSetWriter</code> object is the writer.
155 *
156 * @serial
157 */
158 private CachedRowSetReader reader;
159
160/**
161 * The <code>ResultSetMetaData</code> object that contains information
162 * about the columns in the <code>CachedRowSet</code> object
163 * for which this <code>CachedRowSetWriter</code> object is the writer.
164 *
165 * @serial
166 */
167 private ResultSetMetaData callerMd;
168
169/**
170 * The number of columns in the <code>CachedRowSet</code> object
171 * for which this <code>CachedRowSetWriter</code> object is the writer.
172 *
173 * @serial
174 */
175 private int callerColumnCount;
176
177/**
178 * This <code>CachedRowSet<code> will hold the conflicting values
179 * retrieved from the db and hold it.
180 */
181 private CachedRowSetImpl crsResolve;
182
183/**
184 * This <code>ArrayList<code> will hold the values of SyncResolver.*
185 */
186 private ArrayList status;
187
188/**
189 * This will check whether the same field value has changed both
190 * in database and CachedRowSet.
191 */
192 private int iChangedValsInDbAndCRS;
193
194/**
195 * This will hold the number of cols for which the values have
196 * changed only in database.
197 */
198 private int iChangedValsinDbOnly ;
199
200 private JdbcRowSetResourceBundle resBundle;
201
202 public CachedRowSetWriter() {
203 try {
204 resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
205 } catch(IOException ioe) {
206 throw new RuntimeException(ioe);
207 }
208 }
209
210/**
211 * Propagates changes in the given <code>RowSet</code> object
212 * back to its underlying data source and returns <code>true</code>
213 * if successful. The writer will check to see if
214 * the data in the pre-modified rowset (the original values) differ
215 * from the data in the underlying data source. If data in the data
216 * 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 restricting
220 * access for concurrent users.
221 * <P>
222 * This method is called by the rowset internally when
223 * the application invokes the method <code>acceptChanges</code>.
224 * The <code>writeData</code> method in turn calls private methods that
225 * it defines internally.
226 * The following is a general summary of what the method
227 * <code>writeData</code> does, much of which is accomplished
228 * through calls to its own internal methods.
229 * <OL>
230 * <LI>Creates a <code>CachedRowSet</code> object from the given
231 * <code>RowSet</code> object
232 * <LI>Makes a connection with the data source
233 * <UL>
234 * <LI>Disables autocommit mode if it is not already disabled
235 * <LI>Sets the transaction isolation level to that of the rowset
236 * </UL>
237 * <LI>Checks to see if the reader has read new data since the writer
238 * was last called and, if so, calls the method
239 * <code>initSQLStatements</code> to initialize new SQL statements
240 * <UL>
241 * <LI>Builds new <code>SELECT</code>, <code>UPDATE</code>,
242 * <code>INSERT</code>, and <code>DELETE</code> statements
243 * <LI>Uses the <code>CachedRowSet</code> object's metadata to
244 * determine the table name, column names, and the columns
245 * that make up the primary key
246 * </UL>
247 * <LI>When there is no conflict, propagates changes made to the
248 * <code>CachedRowSet</code> object back to its underlying data source
249 * <UL>
250 * <LI>Iterates through each row of the <code>CachedRowSet</code> object
251 * to determine whether it has been updated, inserted, or deleted
252 * <LI>If the corresponding row in the data source has not been changed
253 * since the rowset last read its
254 * values, the writer will use the appropriate command to update,
255 * insert, or delete the row
256 * <LI>If any data in the data source does not match the original values
257 * for the <code>CachedRowSet</code> object, the writer will roll
258 * 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 successfully
263 * written to the rowset's underlying data source;
264 * <code>false</code> otherwise
265 */
266 public boolean writeData(RowSetInternal caller) throws SQLException {
267 boolean conflict = false;
268 boolean showDel = false;
269 PreparedStatement pstmtIns = null;
270 iChangedValsInDbAndCRS = 0;
271 iChangedValsinDbOnly = 0;
272
273 // We assume caller is a CachedRowSet
274 CachedRowSetImpl crs = (CachedRowSetImpl)caller;
275 // crsResolve = new CachedRowSetImpl();
276 this.crsResolve = new CachedRowSetImpl();;
277
278 // The reader is registered with the writer at design time.
279 // This is not required, in general. The reader has logic
280 // to get a JDBC connection, so call it.
281
282 con = reader.connect(caller);
283
284
285 if (con == null) {
286 throw new SQLException(resBundle.handleGetObject("crswriter.connect").toString());
287 }
288
289 /*
290 // Fix 6200646.
291 // Don't change the connection or transaction properties. This will fail in a
292 // J2EE container.
293 if (con.getAutoCommit() == true) {
294 con.setAutoCommit(false);
295 }
296
297 con.setTransactionIsolation(crs.getTransactionIsolation());
298 */
299
300 initSQLStatements(crs);
301 int iColCount;
302
303 RowSetMetaDataImpl rsmdWrite = (RowSetMetaDataImpl)crs.getMetaData();
304 RowSetMetaDataImpl rsmdResolv = new RowSetMetaDataImpl();
305
306 iColCount = rsmdWrite.getColumnCount();
307 int sz= crs.size()+1;
308 status = new ArrayList(sz);
309
310 status.add(0,null);
311 rsmdResolv.setColumnCount(iColCount);
312
313 for(int i =1; i <= iColCount; i++) {
314 rsmdResolv.setColumnType(i, rsmdWrite.getColumnType(i));
315 rsmdResolv.setColumnName(i, rsmdWrite.getColumnName(i));
316 rsmdResolv.setNullable(i, ResultSetMetaData.columnNullableUnknown);
317 }
318 this.crsResolve.setMetaData(rsmdResolv);
319
320 // moved outside the insert inner loop
321 //pstmtIns = con.prepareStatement(insertCmd);
322
323 if (callerColumnCount < 1) {
324 // No data, so return success.
325 if (reader.getCloseConnection() == true)
326 con.close();
327 return true;
328 }
329 // We need to see rows marked for deletion.
330 showDel = crs.getShowDeleted();
331 crs.setShowDeleted(true);
332
333 // Look at all the rows.
334 crs.beforeFirst();
335
336 int rows =1;
337 while (crs.next()) {
338 if (crs.rowDeleted()) {
339 // The row has been deleted.
340 if (conflict = (deleteOriginalRow(crs, this.crsResolve)) == true) {
341 status.add(rows, Integer.valueOf(SyncResolver.DELETE_ROW_CONFLICT));
342 } else {
343 // delete happened without any occurrence of conflicts
344 // so update status accordingly
345 status.add(rows, Integer.valueOf(SyncResolver.NO_ROW_CONFLICT));
346 }
347
348 } else if (crs.rowInserted()) {
349 // The row has been inserted.
350
351 pstmtIns = con.prepareStatement(insertCmd);
352 if ( (conflict = insertNewRow(crs, pstmtIns, this.crsResolve)) == true) {
353 status.add(rows, Integer.valueOf(SyncResolver.INSERT_ROW_CONFLICT));
354 } else {
355 // insert happened without any occurrence of conflicts
356 // so update status accordingly
357 status.add(rows, Integer.valueOf(SyncResolver.NO_ROW_CONFLICT));
358 }
359 } else if (crs.rowUpdated()) {
360 // The row has been updated.
361 if ( conflict = (updateOriginalRow(crs)) == true) {
362 status.add(rows, Integer.valueOf(SyncResolver.UPDATE_ROW_CONFLICT));
363 } else {
364 // update happened without any occurrence of conflicts
365 // so update status accordingly
366 status.add(rows, Integer.valueOf(SyncResolver.NO_ROW_CONFLICT));
367 }
368
369 } else {
370 /** The row is neither of inserted, updated or deleted.
371 * So set nulls in the this.crsResolve for this row,
372 * as nothing is to be done for such rows.
373 * Also note that if such a row has been changed in database
374 * and we have not changed(inserted, updated or deleted)
375 * that is fine.
376 **/
377 int icolCount = crs.getMetaData().getColumnCount();
378 status.add(rows, Integer.valueOf(SyncResolver.NO_ROW_CONFLICT));
379
380 this.crsResolve.moveToInsertRow();
381 for(int cols=0;cols<iColCount;cols++) {
382 this.crsResolve.updateNull(cols+1);
383 } //end for
384
385 this.crsResolve.insertRow();
386 this.crsResolve.moveToCurrentRow();
387
388 } //end if
389 rows++;
390 } //end while
391
392 // close the insert statement
393 if(pstmtIns!=null)
394 pstmtIns.close();
395 // reset
396 crs.setShowDeleted(showDel);
397
398 boolean boolConf = false;
399 for (int j=1;j<status.size();j++){
400 // ignore status for index = 0 which is set to null
401 if(! ((status.get(j)).equals(Integer.valueOf(SyncResolver.NO_ROW_CONFLICT)))) {
402 // there is at least one conflict which needs to be resolved
403 boolConf = true;
404 break;
405 }
406 }
407
408 crs.beforeFirst();
409 this.crsResolve.beforeFirst();
410
411 if(boolConf) {
412 SyncProviderException spe = new SyncProviderException(status.size() - 1+resBundle.handleGetObject("crswriter.conflictsno").toString());
413 //SyncResolver syncRes = spe.getSyncResolver();
414
415 SyncResolverImpl syncResImpl = (SyncResolverImpl) spe.getSyncResolver();
416
417 syncResImpl.setCachedRowSet(crs);
418 syncResImpl.setCachedRowSetResolver(this.crsResolve);
419
420 syncResImpl.setStatus(status);
421 syncResImpl.setCachedRowSetWriter(this);
422
423 throw spe;
424 } else {
425 return true;
426 }
427 /*
428 if (conflict == true) {
429 con.rollback();
430 return false;
431 } else {
432 con.commit();
433 if (reader.getCloseConnection() == true) {
434 con.close();
435 }
436 return true;
437 }
438 */
439
440 } //end writeData
441
442/**
443 * Updates the given <code>CachedRowSet</code> object's underlying data
444 * source so that updates to the rowset are reflected in the original
445 * data source, and returns <code>false</code> if the update was successful.
446 * A return value of <code>true</code> indicates that there is a conflict,
447 * meaning that a value updated in the rowset has already been changed by
448 * someone else in the underlying data source. A conflict can also exist
449 * if, for example, more than one row in the data source would be affected
450 * by the update or if no rows would be affected. In any case, if there is
451 * a conflict, this method does not update the underlying data source.
452 * <P>
453 * This method is called internally by the method <code>writeData</code>
454 * if a row in the <code>CachedRowSet</code> object for which this
455 * <code>CachedRowSetWriter</code> object is the writer has been updated.
456 *
457 * @return <code>false</code> if the update to the underlying data source is
458 * successful; <code>true</code> otherwise
459 * @throws SQLException if a database access error occurs
460 */
461 private boolean updateOriginalRow(CachedRowSet crs)
462 throws SQLException {
463 PreparedStatement pstmt;
464 int i = 0;
465 int idx = 0;
466
467 // Select the row from the database.
468 ResultSet origVals = crs.getOriginalRow();
469 origVals.next();
470
471 try {
472 updateWhere = buildWhereClause(updateWhere, origVals);
473
474
475 /**
476 * The following block of code is for checking a particular type of
477 * query where in there is a where clause. Without this block, if a
478 * SQL statement is built the "where" clause will appear twice hence
479 * the DB errors out and a SQLException is thrown. This code also
480 * considers that the where clause is in the right place as the
481 * CachedRowSet object would already have been populated with this
482 * query before coming to this point.
483 **/
484
485
486 String tempselectCmd = selectCmd.toLowerCase();
487
488 int idxWhere = tempselectCmd.indexOf("where");
489
490 if(idxWhere != -1)
491 {
492 String tempSelect = selectCmd.substring(0,idxWhere);
493 selectCmd = tempSelect;
494 }
495
496 pstmt = con.prepareStatement(selectCmd + updateWhere,
497 ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
498
499 for (i = 0; i < keyCols.length; i++) {
500 if (params[i] != null) {
501 pstmt.setObject(++idx, params[i]);
502 } else {
503 continue;
504 }
505 }
506
507 try {
508 pstmt.setMaxRows(crs.getMaxRows());
509 pstmt.setMaxFieldSize(crs.getMaxFieldSize());
510 pstmt.setEscapeProcessing(crs.getEscapeProcessing());
511 pstmt.setQueryTimeout(crs.getQueryTimeout());
512 } catch (Exception ex) {
513 // Older driver don't support these operations.
514 }
515
516 ResultSet rs = null;
517 rs = pstmt.executeQuery();
518 ResultSetMetaData rsmd = rs.getMetaData();
519
520 if (rs.next()) {
521 if (rs.next()) {
522 /** More than one row conflict.
523 * If rs has only one row we are able to
524 * uniquely identify the row where update
525 * have to happen else if more than one
526 * row implies we cannot uniquely identify the row
527 * where we have to do updates.
528 * crs.setKeyColumns needs to be set to
529 * come out of this situation.
530 */
531
532 return true;
533 }
534
535 // don't close the rs
536 // we require the record in rs to be used.
537 // rs.close();
538 // pstmt.close();
539 rs.first();
540
541 // how many fields need to be updated
542 int colsNotChanged = 0;
543 Vector cols = new Vector();
544 String updateExec = updateCmd;
545 Object orig;
546 Object curr;
547 Object rsval;
548 boolean boolNull = true;
549 Object objVal = null;
550
551 // There's only one row and the cursor
552 // needs to be on that row.
553
554 boolean first = true;
555 boolean flag = true;
556
557 this.crsResolve.moveToInsertRow();
558
559 for (i = 1; i <= callerColumnCount; i++) {
560 orig = origVals.getObject(i);
561 curr = crs.getObject(i);
562 rsval = rs.getObject(i);
563 /*
564 * the following block creates equivalent objects
565 * that would have been created if this rs is populated
566 * into a CachedRowSet so that comparison of the column values
567 * from the ResultSet and CachedRowSet are possible
568 */
569 Map map = (crs.getTypeMap() == null)?con.getTypeMap():crs.getTypeMap();
570 if (rsval instanceof Struct) {
571
572 Struct s = (Struct)rsval;
573
574 // look up the class in the map
575 Class c = null;
576 c = (Class)map.get(s.getSQLTypeName());
577 if (c != null) {
578 // create new instance of the class
579 SQLData obj = null;
580 try {
581 obj = (SQLData)c.newInstance();
582 } catch (java.lang.InstantiationException ex) {
583 throw new SQLException(MessageFormat.format(resBundle.handleGetObject("cachedrowsetimpl.unableins").toString(),
584 ex.getMessage()));
585 } catch (java.lang.IllegalAccessException ex) {
586 throw new SQLException(MessageFormat.format(resBundle.handleGetObject("cachedrowsetimpl.unableins").toString(),
587 ex.getMessage()));
588 }
589 // get the attributes from the struct
590 Object attribs[] = s.getAttributes(map);
591 // create the SQLInput "stream"
592 SQLInputImpl sqlInput = new SQLInputImpl(attribs, map);
593 // read the values...
594 obj.readSQL(sqlInput, s.getSQLTypeName());
595 rsval = obj;
596 }
597 } else if (rsval instanceof SQLData) {
598 rsval = new SerialStruct((SQLData)rsval, map);
599 } else if (rsval instanceof Blob) {
600 rsval = new SerialBlob((Blob)rsval);
601 } else if (rsval instanceof Clob) {
602 rsval = new SerialClob((Clob)rsval);
603 } else if (rsval instanceof java.sql.Array) {
604 rsval = new SerialArray((java.sql.Array)rsval, map);
605 }
606
607 // reset boolNull if it had been set
608 boolNull = true;
609
610 /** This addtional checking has been added when the current value
611 * in the DB is null, but the DB had a different value when the
612 * data was actaully fetched into the CachedRowSet.
613 **/
614
615 if(rsval == null && orig != null) {
616 // value in db has changed
617 // don't proceed with synchronization
618 // get the value in db and pass it to the resolver.
619
620 iChangedValsinDbOnly++;
621 // Set the boolNull to false,
622 // in order to set the actual value;
623 boolNull = false;
624 objVal = rsval;
625 }
626
627 /** Adding the checking for rsval to be "not" null or else
628 * it would through a NullPointerException when the values
629 * are compared.
630 **/
631
632 else if(rsval != null && (!rsval.equals(orig)))
633 {
634 // value in db has changed
635 // don't proceed with synchronization
636 // get the value in db and pass it to the resolver.
637
638 iChangedValsinDbOnly++;
639 // Set the boolNull to false,
640 // in order to set the actual value;
641 boolNull = false;
642 objVal = rsval;
643 } else if ( (orig == null || curr == null) ) {
644
645 /** Adding the additonal condition of checking for "flag"
646 * boolean variable, which would otherwise result in
647 * building a invalid query, as the comma would not be
648 * added to the query string.
649 **/
650
651 if (first == false || flag == false) {
652 updateExec += ", ";
653 }
654 updateExec += crs.getMetaData().getColumnName(i);
655 cols.add(Integer.valueOf(i));
656 updateExec += " = ? ";
657 first = false;
658
659 /** Adding the extra condition for orig to be "not" null as the
660 * condition for orig to be null is take prior to this, if this
661 * is not added it will result in a NullPointerException when
662 * the values are compared.
663 **/
664
665 } else if (orig.equals(curr)) {
666 colsNotChanged++;
667 //nothing to update in this case since values are equal
668
669 /** Adding the extra condition for orig to be "not" null as the
670 * condition for orig to be null is take prior to this, if this
671 * is not added it will result in a NullPointerException when
672 * the values are compared.
673 **/
674
675 } else if(orig.equals(curr) == false) {
676 // When values from db and values in CachedRowSet are not equal,
677 // if db value is same as before updation for each col in
678 // the row before fetching into CachedRowSet,
679 // only then we go ahead with updation, else we
680 // throw SyncProviderException.
681
682 // if value has changed in db after fetching from db
683 // for some cols of the row and at the same time, some other cols
684 // have changed in CachedRowSet, no synchronization happens
685
686 // Synchronization happens only when data when fetching is
687 // same or at most has changed in cachedrowset
688
689 // check orig value with what is there in crs for a column
690 // before updation in crs.
691
692 if(crs.columnUpdated(i)) {
693 if(rsval.equals(orig)) {
694 // At this point we are sure that
695 // the value updated in crs was from
696 // what is in db now and has not changed
697 if (flag == false || first == false) {
698 updateExec += ", ";
699 }
700 updateExec += crs.getMetaData().getColumnName(i);
701 cols.add(Integer.valueOf(i));
702 updateExec += " = ? ";
703 flag = false;
704 } else {
705 // Here the value has changed in the db after
706 // data was fetched
707 // Plus store this row from CachedRowSet and keep it
708 // in a new CachedRowSet
709 boolNull= false;
710 objVal = rsval;
711 iChangedValsInDbAndCRS++;
712 }
713 }
714 }
715
716 if(!boolNull) {
717 this.crsResolve.updateObject(i,objVal);
718 } else {
719 this.crsResolve.updateNull(i);
720 }
721 } //end for
722
723 rs.close();
724 pstmt.close();
725
726 this.crsResolve.insertRow();
727 this.crsResolve.moveToCurrentRow();
728
729 /**
730 * if nothing has changed return now - this can happen
731 * if column is updated to the same value.
732 * if colsNotChanged == callerColumnCount implies we are updating
733 * the database with ALL COLUMNS HAVING SAME VALUES,
734 * so skip going to database, else do as usual.
735 **/
736 if ( (first == false && cols.size() == 0) ||
737 colsNotChanged == callerColumnCount ) {
738 return false;
739 }
740
741 if(iChangedValsInDbAndCRS != 0 || iChangedValsinDbOnly != 0) {
742 return true;
743 }
744
745
746 updateExec += updateWhere;
747
748 pstmt = con.prepareStatement(updateExec);
749
750 // Comments needed here
751 for (i = 0; i < cols.size(); i++) {
752 Object obj = crs.getObject(((Integer)cols.get(i)).intValue());
753 if (obj != null)
754 pstmt.setObject(i + 1, obj);
755 else
756 pstmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));
757 }
758 idx = i;
759
760 // Comments needed here
761 for (i = 0; i < keyCols.length; i++) {
762 if (params[i] != null) {
763 pstmt.setObject(++idx, params[i]);
764 } else {
765 continue;
766 }
767 }
768
769 i = pstmt.executeUpdate();
770
771 /**
772 * i should be equal to 1(row count), because we update
773 * one row(returned as row count) at a time, if all goes well.
774 * if 1 != 1, this implies we have not been able to
775 * do updations properly i.e there is a conflict in database
776 * versus what is in CachedRowSet for this particular row.
777 **/
778
779 return false;
780
781 } else {
782 /**
783 * Cursor will be here, if the ResultSet may not return even a single row
784 * i.e. we can't find the row where to update because it has been deleted
785 * etc. from the db.
786 * Present the whole row as null to user, to force null to be sync'ed
787 * and hence nothing to be synced.
788 *
789 * NOTE:
790 * ------
791 * In the database if a column that is mapped to java.sql.Types.REAL stores
792 * a Double value and is compared with value got from ResultSet.getFloat()
793 * no row is retrieved and will throw a SyncProviderException. For details
794 * see bug Id 5053830
795 **/
796 return true;
797 }
798 } catch (SQLException ex) {
799 ex.printStackTrace();
800 // if executeUpdate fails it will come here,
801 // update crsResolve with null rows
802 this.crsResolve.moveToInsertRow();
803
804 for(i = 1; i <= callerColumnCount; i++) {
805 this.crsResolve.updateNull(i);
806 }
807
808 this.crsResolve.insertRow();
809 this.crsResolve.moveToCurrentRow();
810
811 return true;
812 }
813 }
814
815 /**
816 * Inserts a row that has been inserted into the given
817 * <code>CachedRowSet</code> object into the data source from which
818 * the rowset is derived, returning <code>false</code> if the insertion
819 * was successful.
820 *
821 * @param crs the <code>CachedRowSet</code> object that has had a row inserted
822 * and to whose underlying data source the row will be inserted
823 * @param pstmt the <code>PreparedStatement</code> object that will be used
824 * to execute the insertion
825 * @return <code>false</code> to indicate that the insertion was successful;
826 * <code>true</code> otherwise
827 * @throws SQLException if a database access error occurs
828 */
829 private boolean insertNewRow(CachedRowSet crs,
830 PreparedStatement pstmt, CachedRowSetImpl crsRes) throws SQLException {
831 int i = 0;
832 int icolCount = crs.getMetaData().getColumnCount();
833
834 boolean returnVal = false;
835 PreparedStatement pstmtSel = con.prepareStatement(selectCmd,
836 ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
837 ResultSet rs, rs2 = null;
838 DatabaseMetaData dbmd = con.getMetaData();
839 rs = pstmtSel.executeQuery();
840 String table = crs.getTableName();
841 rs2 = dbmd.getPrimaryKeys(null, null, table);
842 String [] primaryKeys = new String[icolCount];
843 int k = 0;
844 while(rs2.next()) {
845 String pkcolname = rs2.getString("COLUMN_NAME");
846 primaryKeys[k] = pkcolname;
847 k++;
848 }
849
850 if(rs.next()) {
851 for(int j=0;j<primaryKeys.length;j++) {
852 if(primaryKeys[j] != null) {
853 if(crs.getObject(primaryKeys[j]) == null){
854 break;
855 }
856 String crsPK = (crs.getObject(primaryKeys[j])).toString();
857 String rsPK = (rs.getObject(primaryKeys[j])).toString();
858 if(crsPK.equals(rsPK)) {
859 returnVal = true;
860 this.crsResolve.moveToInsertRow();
861 for(i = 1; i <= icolCount; i++) {
862 String colname = (rs.getMetaData()).getColumnName(i);
863 if(colname.equals(primaryKeys[j]))
864 this.crsResolve.updateObject(i,rsPK);
865 else
866 this.crsResolve.updateNull(i);
867 }
868 this.crsResolve.insertRow();
869 this.crsResolve.moveToCurrentRow();
870 }
871 }
872 }
873 }
874 if(returnVal)
875 return returnVal;
876
877 try {
878 for (i = 1; i <= icolCount; i++) {
879 Object obj = crs.getObject(i);
880 if (obj != null) {
881 pstmt.setObject(i, obj);
882 } else {
883 pstmt.setNull(i,crs.getMetaData().getColumnType(i));
884 }
885 }
886
887 i = pstmt.executeUpdate();
888 return false;
889
890 } catch (SQLException ex) {
891 /**
892 * Cursor will come here if executeUpdate fails.
893 * There can be many reasons why the insertion failed,
894 * one can be violation of primary key.
895 * Hence we cannot exactly identify why the insertion failed
896 * Present the current row as a null row to the user.
897 **/
898 this.crsResolve.moveToInsertRow();
899
900 for(i = 1; i <= icolCount; i++) {
901 this.crsResolve.updateNull(i);
902 }
903
904 this.crsResolve.insertRow();
905 this.crsResolve.moveToCurrentRow();
906
907 return true;
908 }
909 }
910
911/**
912 * Deletes the row in the underlying data source that corresponds to
913 * a row that has been deleted in the given <code> CachedRowSet</code> object
914 * and returns <code>false</code> if the deletion was successful.
915 * <P>
916 * This method is called internally by this writer's <code>writeData</code>
917 * method when a row in the rowset has been deleted. The values in the
918 * deleted row are the same as those that are stored in the original row
919 * of the given <code>CachedRowSet</code> object. If the values in the
920 * original row differ from the row in the underlying data source, the row
921 * in the data source is not deleted, and <code>deleteOriginalRow</code>
922 * returns <code>true</code> to indicate that there was a conflict.
923 *
924 *
925 * @return <code>false</code> if the deletion was successful, which means that
926 * there was no conflict; <code>true</code> otherwise
927 * @throws SQLException if there was a database access error
928 */
929 private boolean deleteOriginalRow(CachedRowSet crs, CachedRowSetImpl crsRes) throws SQLException {
930 PreparedStatement pstmt;
931 int i;
932 int idx = 0;
933 String strSelect;
934 // Select the row from the database.
935 ResultSet origVals = crs.getOriginalRow();
936 origVals.next();
937
938 deleteWhere = buildWhereClause(deleteWhere, origVals);
939 pstmt = con.prepareStatement(selectCmd + deleteWhere,
940 ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
941
942 for (i = 0; i < keyCols.length; i++) {
943 if (params[i] != null) {
944 pstmt.setObject(++idx, params[i]);
945 } else {
946 continue;
947 }
948 }
949
950 try {
951 pstmt.setMaxRows(crs.getMaxRows());
952 pstmt.setMaxFieldSize(crs.getMaxFieldSize());
953 pstmt.setEscapeProcessing(crs.getEscapeProcessing());
954 pstmt.setQueryTimeout(crs.getQueryTimeout());
955 } catch (Exception ex) {
956 /*
957 * Older driver don't support these operations...
958 */
959 ;
960 }
961
962 ResultSet rs = pstmt.executeQuery();
963
964 if (rs.next() == true) {
965 if (rs.next()) {
966 // more than one row
967 return true;
968 }
969 rs.first();
970
971 // Now check all the values in rs to be same in
972 // db also before actually going ahead with deleting
973 boolean boolChanged = false;
974
975 crsRes.moveToInsertRow();
976
977 for (i = 1; i <= crs.getMetaData().getColumnCount(); i++) {
978
979 Object original = origVals.getObject(i);
980 Object changed = rs.getObject(i);
981
982 if(original != null && changed != null ) {
983 if(! (original.toString()).equals(changed.toString()) ) {
984 boolChanged = true;
985 crsRes.updateObject(i,origVals.getObject(i));
986 }
987 } else {
988 crsRes.updateNull(i);
989 }
990 }
991
992 crsRes.insertRow();
993 crsRes.moveToCurrentRow();
994
995 if(boolChanged) {
996 // do not delete as values in db have changed
997 // deletion will not happen for this row from db
998 // exit now returning true. i.e. conflict
999 return true;
1000 } else {
1001 // delete the row.
1002 // Go ahead with deleting,
1003 // don't do anything here
1004 }
1005
1006 String cmd = deleteCmd + deleteWhere;
1007 pstmt = con.prepareStatement(cmd);
1008
1009 idx = 0;
1010 for (i = 0; i < keyCols.length; i++) {
1011 if (params[i] != null) {
1012 pstmt.setObject(++idx, params[i]);
1013 } else {
1014 continue;
1015 }
1016 }
1017
1018 if (pstmt.executeUpdate() != 1) {
1019 return true;
1020 }
1021 pstmt.close();
1022 } else {
1023 // didn't find the row
1024 return true;
1025 }
1026
1027 // no conflict
1028 return false;
1029 }
1030
1031 /**
1032 * Sets the reader for this writer to the given reader.
1033 *
1034 * @throws SQLException if a database access error occurs
1035 */
1036 public void setReader(CachedRowSetReader reader) throws SQLException {
1037 this.reader = reader;
1038 }
1039
1040 /**
1041 * Gets the reader for this writer.
1042 *
1043 * @throws SQLException if a database access error occurs
1044 */
1045 public CachedRowSetReader getReader() throws SQLException {
1046 return reader;
1047 }
1048
1049 /**
1050 * Composes a <code>SELECT</code>, <code>UPDATE</code>, <code>INSERT</code>,
1051 * and <code>DELETE</code> statement that can be used by this writer to
1052 * write data to the data source backing the given <code>CachedRowSet</code>
1053 * object.
1054 *
1055 * @ param caller a <code>CachedRowSet</code> object for which this
1056 * <code>CachedRowSetWriter</code> object is the writer
1057 * @throws SQLException if a database access error occurs
1058 */
1059 private void initSQLStatements(CachedRowSet caller) throws SQLException {
1060
1061 int i;
1062
1063 callerMd = caller.getMetaData();
1064 callerColumnCount = callerMd.getColumnCount();
1065 if (callerColumnCount < 1)
1066 // No data, so return.
1067 return;
1068
1069 /*
1070 * If the RowSet has a Table name we should use it.
1071 * This is really a hack to get round the fact that
1072 * a lot of the jdbc drivers can't provide the tab.
1073 */
1074 String table = caller.getTableName();
1075 if (table == null) {
1076 /*
1077 * attempt to build a table name using the info
1078 * that the driver gave us for the first column
1079 * in the source result set.
1080 */
1081 table = callerMd.getTableName(1);
1082 if (table == null || table.length() == 0) {
1083 throw new SQLException(resBundle.handleGetObject("crswriter.tname").toString());
1084 }
1085 }
1086 String catalog = callerMd.getCatalogName(1);
1087 String schema = callerMd.getSchemaName(1);
1088 DatabaseMetaData dbmd = con.getMetaData();
1089
1090 /*
1091 * Compose a SELECT statement. There are three parts.
1092 */
1093
1094 // Project List
1095 selectCmd = "SELECT ";
1096 for (i=1; i <= callerColumnCount; i++) {
1097 selectCmd += callerMd.getColumnName(i);
1098 if ( i < callerMd.getColumnCount() )
1099 selectCmd += ", ";
1100 else
1101 selectCmd += " ";
1102 }
1103
1104 // FROM clause.
1105 selectCmd += "FROM " + buildTableName(dbmd, catalog, schema, table);
1106
1107 /*
1108 * Compose an UPDATE statement.
1109 */
1110 updateCmd = "UPDATE " + buildTableName(dbmd, catalog, schema, table);
1111
1112
1113 /**
1114 * The following block of code is for checking a particular type of
1115 * query where in there is a where clause. Without this block, if a
1116 * SQL statement is built the "where" clause will appear twice hence
1117 * the DB errors out and a SQLException is thrown. This code also
1118 * considers that the where clause is in the right place as the
1119 * CachedRowSet object would already have been populated with this
1120 * query before coming to this point.
1121 **/
1122
1123 String tempupdCmd = updateCmd.toLowerCase();
1124
1125 int idxupWhere = tempupdCmd.indexOf("where");
1126
1127 if(idxupWhere != -1)
1128 {
1129 updateCmd = updateCmd.substring(0,idxupWhere);
1130 }
1131 updateCmd += "SET ";
1132
1133 /*
1134 * Compose an INSERT statement.
1135 */
1136 insertCmd = "INSERT INTO " + buildTableName(dbmd, catalog, schema, table);
1137 // Column list
1138 insertCmd += "(";
1139 for (i=1; i <= callerColumnCount; i++) {
1140 insertCmd += callerMd.getColumnName(i);
1141 if ( i < callerMd.getColumnCount() )
1142 insertCmd += ", ";
1143 else
1144 insertCmd += ") VALUES (";
1145 }
1146 for (i=1; i <= callerColumnCount; i++) {
1147 insertCmd += "?";
1148 if (i < callerColumnCount)
1149 insertCmd += ", ";
1150 else
1151 insertCmd += ")";
1152 }
1153
1154 /*
1155 * Compose a DELETE statement.
1156 */
1157 deleteCmd = "DELETE FROM " + buildTableName(dbmd, catalog, schema, table);
1158
1159 /*
1160 * set the key desriptors that will be
1161 * needed to construct where clauses.
1162 */
1163 buildKeyDesc(caller);
1164 }
1165
1166 /**
1167 * Returns a fully qualified table name built from the given catalog and
1168 * table names. The given metadata object is used to get the proper order
1169 * and separator.
1170 *
1171 * @param dbmd a <code>DatabaseMetaData</code> object that contains metadata
1172 * about this writer's <code>CachedRowSet</code> object
1173 * @param catalog a <code>String</code> object with the rowset's catalog
1174 * name
1175 * @param table a <code>String</code> object with the name of the table from
1176 * which this writer's rowset was derived
1177 * @return a <code>String</code> object with the fully qualified name of the
1178 * table from which this writer's rowset was derived
1179 * @throws SQLException if a database access error occurs
1180 */
1181 private String buildTableName(DatabaseMetaData dbmd,
1182 String catalog, String schema, String table) throws SQLException {
1183
1184 // trim all the leading and trailing whitespaces,
1185 // white spaces can never be catalog, schema or a table name.
1186
1187 String cmd = "";
1188
1189 catalog = catalog.trim();
1190 schema = schema.trim();
1191 table = table.trim();
1192
1193 if (dbmd.isCatalogAtStart() == true) {
1194 if (catalog != null && catalog.length() > 0) {
1195 cmd += catalog + dbmd.getCatalogSeparator();
1196 }
1197 if (schema != null && schema.length() > 0) {
1198 cmd += schema + ".";
1199 }
1200 cmd += table;
1201 } else {
1202 if (schema != null && schema.length() > 0) {
1203 cmd += schema + ".";
1204 }
1205 cmd += table;
1206 if (catalog != null && catalog.length() > 0) {
1207 cmd += dbmd.getCatalogSeparator() + catalog;
1208 }
1209 }
1210 cmd += " ";
1211 return cmd;
1212 }
1213
1214 /**
1215 * Assigns to the given <code>CachedRowSet</code> object's
1216 * <code>params</code>
1217 * field an array whose length equals the number of columns needed
1218 * to uniquely identify a row in the rowset. The array is given
1219 * values by the method <code>buildWhereClause</code>.
1220 * <P>
1221 * If the <code>CachedRowSet</code> object's <code>keyCols</code>
1222 * field has length <code>0</code> or is <code>null</code>, the array
1223 * is set with the column number of every column in the rowset.
1224 * Otherwise, the array in the field <code>keyCols</code> is set with only
1225 * the column numbers of the columns that are required to form a unique
1226 * identifier for a row.
1227 *
1228 * @param crs the <code>CachedRowSet</code> object for which this
1229 * <code>CachedRowSetWriter</code> object is the writer
1230 *
1231 * @throws SQLException if a database access error occurs
1232 */
1233 private void buildKeyDesc(CachedRowSet crs) throws SQLException {
1234
1235 keyCols = crs.getKeyColumns();
1236 ResultSetMetaData resultsetmd = crs.getMetaData();
1237 if (keyCols == null || keyCols.length == 0) {
1238 ArrayList<Integer> listKeys = new ArrayList<Integer>();
1239
1240 for (int i = 0; i < callerColumnCount; i++ ) {
1241 if(resultsetmd.getColumnType(i+1) != java.sql.Types.CLOB &&
1242 resultsetmd.getColumnType(i+1) != java.sql.Types.STRUCT &&
1243 resultsetmd.getColumnType(i+1) != java.sql.Types.SQLXML &&
1244 resultsetmd.getColumnType(i+1) != java.sql.Types.BLOB &&
1245 resultsetmd.getColumnType(i+1) != java.sql.Types.ARRAY &&
1246 resultsetmd.getColumnType(i+1) != java.sql.Types.OTHER )
1247 listKeys.add(i+1);
1248 }
1249 keyCols = new int[listKeys.size()];
1250 for (int i = 0; i < listKeys.size(); i++ )
1251 keyCols[i] = listKeys.get(i);
1252 }
1253 params = new Object[keyCols.length];
1254 }
1255
1256 /**
1257 * Constructs an SQL <code>WHERE</code> clause using the given
1258 * string as a starting point. The resulting clause will contain
1259 * a column name and " = ?" for each key column, that is, each column
1260 * that is needed to form a unique identifier for a row in the rowset.
1261 * This <code>WHERE</code> clause can be added to
1262 * a <code>PreparedStatement</code> object that updates, inserts, or
1263 * deletes a row.
1264 * <P>
1265 * This method uses the given result set to access values in the
1266 * <code>CachedRowSet</code> object that called this writer. These
1267 * values are used to build the array of parameters that will serve as
1268 * replacements for the "?" parameter placeholders in the
1269 * <code>PreparedStatement</code> object that is sent to the
1270 * <code>CachedRowSet</code> object's underlying data source.
1271 *
1272 * @param whereClause a <code>String</code> object that is an empty
1273 * string ("")
1274 * @param rs a <code>ResultSet</code> object that can be used
1275 * to access the <code>CachedRowSet</code> object's data
1276 * @return a <code>WHERE</code> clause of the form "<code>WHERE</code>
1277 * columnName = ? AND columnName = ? AND columnName = ? ..."
1278 * @throws SQLException if a database access error occurs
1279 */
1280 private String buildWhereClause(String whereClause,
1281 ResultSet rs) throws SQLException {
1282 whereClause = "WHERE ";
1283
1284 for (int i = 0; i < keyCols.length; i++) {
1285 if (i > 0) {
1286 whereClause += "AND ";
1287 }
1288 whereClause += callerMd.getColumnName(keyCols[i]);
1289 params[i] = rs.getObject(keyCols[i]);
1290 if (rs.wasNull() == true) {
1291 whereClause += " IS NULL ";
1292 } else {
1293 whereClause += " = ? ";
1294 }
1295 }
1296 return whereClause;
1297 }
1298
1299 void updateResolvedConflictToDB(CachedRowSet crs, Connection con) throws SQLException {
1300 //String updateExe = ;
1301 PreparedStatement pStmt ;
1302 String strWhere = "WHERE " ;
1303 String strExec =" ";
1304 String strUpdate = "UPDATE ";
1305 int icolCount = crs.getMetaData().getColumnCount();
1306 int keyColumns[] = crs.getKeyColumns();
1307 Object param[];
1308 String strSet="";
1309
1310 strWhere = buildWhereClause(strWhere, crs);
1311
1312 if (keyColumns == null || keyColumns.length == 0) {
1313 keyColumns = new int[icolCount];
1314 for (int i = 0; i < keyColumns.length; ) {
1315 keyColumns[i] = ++i;
1316 }
1317 }
1318 param = new Object[keyColumns.length];
1319
1320 strUpdate = "UPDATE " + buildTableName(con.getMetaData(),
1321 crs.getMetaData().getCatalogName(1),
1322 crs.getMetaData().getSchemaName(1),
1323 crs.getTableName());
1324
1325 // changed or updated values will become part of
1326 // set clause here
1327 strUpdate += "SET ";
1328
1329 boolean first = true;
1330
1331 for (int i=1; i<=icolCount;i++) {
1332 if (crs.columnUpdated(i)) {
1333 if (first == false) {
1334 strSet += ", ";
1335 }
1336 strSet += crs.getMetaData().getColumnName(i);
1337 strSet += " = ? ";
1338 first = false;
1339 } //end if
1340 } //end for
1341
1342 // keycols will become part of where clause
1343 strUpdate += strSet;
1344 strWhere = "WHERE ";
1345
1346 for (int i = 0; i < keyColumns.length; i++) {
1347 if (i > 0) {
1348 strWhere += "AND ";
1349 }
1350 strWhere += crs.getMetaData().getColumnName(keyColumns[i]);
1351 param[i] = crs.getObject(keyColumns[i]);
1352 if (crs.wasNull() == true) {
1353 strWhere += " IS NULL ";
1354 } else {
1355 strWhere += " = ? ";
1356 }
1357 }
1358 strUpdate += strWhere;
1359
1360 pStmt = con.prepareStatement(strUpdate);
1361
1362 int idx =0;
1363 for (int i = 0; i < icolCount; i++) {
1364 if(crs.columnUpdated(i+1)) {
1365 Object obj = crs.getObject(i+1);
1366 if (obj != null) {
1367 pStmt.setObject(++idx, obj);
1368 } else {
1369 pStmt.setNull(i + 1,crs.getMetaData().getColumnType(i + 1));
1370 } //end if ..else
1371 } //end if crs.column...
1372 } //end for
1373
1374 // Set the key cols for after WHERE =? clause
1375 for (int i = 0; i < keyColumns.length; i++) {
1376 if (param[i] != null) {
1377 pStmt.setObject(++idx, param[i]);
1378 }
1379 }
1380
1381 int id = pStmt.executeUpdate();
1382 }
1383
1384
1385 /**
1386 *
1387 */
1388 public void commit() throws SQLException {
1389 con.commit();
1390 if (reader.getCloseConnection() == true) {
1391 con.close();
1392 }
1393 }
1394
1395 public void commit(CachedRowSetImpl crs, boolean updateRowset) throws SQLException {
1396 con.commit();
1397 if(updateRowset) {
1398 if(crs.getCommand() != null)
1399 crs.execute(con);
1400 }
1401
1402 if (reader.getCloseConnection() == true) {
1403 con.close();
1404 }
1405 }
1406
1407 /**
1408 *
1409 */
1410 public void rollback() throws SQLException {
1411 con.rollback();
1412 if (reader.getCloseConnection() == true) {
1413 con.close();
1414 }
1415 }
1416
1417 /**
1418 *
1419 */
1420 public void rollback(Savepoint s) throws SQLException {
1421 con.rollback(s);
1422 if (reader.getCloseConnection() == true) {
1423 con.close();
1424 }
1425 }
1426
1427 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
1428 // Default state initialization happens here
1429 ois.defaultReadObject();
1430 // Initialization of Res Bundle happens here .
1431 try {
1432 resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
1433 } catch(IOException ioe) {
1434 throw new RuntimeException(ioe);
1435 }
1436
1437 }
1438
1439 static final long serialVersionUID =-8506030970299413976L;
1440}