1 package org.apache.torque.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.Serializable;
23 import java.sql.Connection;
24 import java.sql.PreparedStatement;
25 import java.sql.SQLException;
26 import java.sql.Statement;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.Hashtable;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.apache.torque.Database;
40 import org.apache.torque.Torque;
41 import org.apache.torque.TorqueException;
42 import org.apache.torque.adapter.DB;
43 import org.apache.torque.map.ColumnMap;
44 import org.apache.torque.map.DatabaseMap;
45 import org.apache.torque.map.MapBuilder;
46 import org.apache.torque.map.TableMap;
47 import org.apache.torque.oid.IdGenerator;
48 import org.apache.torque.om.NumberKey;
49 import org.apache.torque.om.ObjectKey;
50 import org.apache.torque.om.SimpleKey;
51 import org.apache.torque.om.StringKey;
52
53 import com.workingdogs.village.Column;
54 import com.workingdogs.village.DataSet;
55 import com.workingdogs.village.DataSetException;
56 import com.workingdogs.village.KeyDef;
57 import com.workingdogs.village.QueryDataSet;
58 import com.workingdogs.village.Record;
59 import com.workingdogs.village.Schema;
60 import com.workingdogs.village.TableDataSet;
61
62 /***
63 * This is the base class for all Peer classes in the system. Peer
64 * classes are responsible for isolating all of the database access
65 * for a specific business object. They execute all of the SQL
66 * against the database. Over time this class has grown to include
67 * utility methods which ease execution of cross-database queries and
68 * the implementation of concrete Peers.
69 *
70 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
71 * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
72 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
73 * @author <a href="mailto:stephenh@chase3000.com">Stephen Haberman</a>
74 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
75 * @author <a href="mailto:vido@ldh.org">Augustin Vidovic</a>
76 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
77 * @version $Id: BasePeer.java 473821 2006-11-11 22:37:25Z tv $
78 */
79 public abstract class BasePeer
80 implements Serializable
81 {
82 /*** Constant criteria key to reference ORDER BY columns. */
83 public static final String ORDER_BY = "ORDER BY";
84
85 /***
86 * Constant criteria key to remove Case Information from
87 * search/ordering criteria.
88 */
89 public static final String IGNORE_CASE = "IgNOrE cAsE";
90
91 /*** Classes that implement this class should override this value. */
92 public static final String TABLE_NAME = "TABLE_NAME";
93
94 /*** Hashtable that contains the cached mapBuilders. */
95 private static Hashtable mapBuilders = new Hashtable(5);
96
97 /*** the log */
98 protected static final Log log = LogFactory.getLog(BasePeer.class);
99
100 private static void throwTorqueException(Exception e)
101 throws TorqueException
102 {
103 if (e instanceof TorqueException)
104 {
105 throw (TorqueException) e;
106 }
107 else
108 {
109 throw new TorqueException(e);
110 }
111 }
112
113 /***
114 * Sets up a Schema for a table. This schema is then normally
115 * used as the argument for initTableColumns().
116 *
117 * @param tableName The name of the table.
118 * @return A Schema.
119 */
120 public static Schema initTableSchema(String tableName)
121 {
122 return initTableSchema(tableName, Torque.getDefaultDB());
123 }
124
125 /***
126 * Sets up a Schema for a table. This schema is then normally
127 * used as the argument for initTableColumns
128 *
129 * @param tableName The propery name for the database in the
130 * configuration file.
131 * @param dbName The name of the database.
132 * @return A Schema.
133 */
134 public static Schema initTableSchema(String tableName, String dbName)
135 {
136 Schema schema = null;
137 Connection con = null;
138
139 try
140 {
141 con = Torque.getConnection(dbName);
142 schema = new Schema().schema(con, tableName);
143 }
144 catch (Exception e)
145 {
146 log.error(e);
147 throw new Error("Error in BasePeer.initTableSchema("
148 + tableName
149 + "): "
150 + e.getMessage());
151 }
152 finally
153 {
154 Torque.closeConnection(con);
155 }
156 return schema;
157 }
158
159 /***
160 * Creates a Column array for a table based on its Schema.
161 *
162 * @param schema A Schema object.
163 * @return A Column[].
164 */
165 public static Column[] initTableColumns(Schema schema)
166 {
167 Column[] columns = null;
168 try
169 {
170 int numberOfColumns = schema.numberOfColumns();
171 columns = new Column[numberOfColumns];
172 for (int i = 0; i < numberOfColumns; i++)
173 {
174 columns[i] = schema.column(i + 1);
175 }
176 }
177 catch (Exception e)
178 {
179 log.error(e);
180 throw new Error(
181 "Error in BasePeer.initTableColumns(): " + e.getMessage());
182 }
183 return columns;
184 }
185
186 /***
187 * Convenience method to create a String array of column names.
188 *
189 * @param columns A Column[].
190 * @return A String[].
191 */
192 public static String[] initColumnNames(Column[] columns)
193 {
194 String[] columnNames = new String[columns.length];
195 for (int i = 0; i < columns.length; i++)
196 {
197 columnNames[i] = columns[i].name().toUpperCase();
198 }
199 return columnNames;
200 }
201
202 /***
203 * Convenience method to create a String array of criteria keys.
204 *
205 * @param tableName Name of table.
206 * @param columnNames A String[].
207 * @return A String[].
208 */
209 public static String[] initCriteriaKeys(
210 String tableName,
211 String[] columnNames)
212 {
213 String[] keys = new String[columnNames.length];
214 for (int i = 0; i < columnNames.length; i++)
215 {
216 keys[i] = tableName + "." + columnNames[i].toUpperCase();
217 }
218 return keys;
219 }
220
221 /***
222 * Convenience method that uses straight JDBC to delete multiple
223 * rows. Village throws an Exception when multiple rows are
224 * deleted.
225 *
226 * @param con A Connection.
227 * @param table The table to delete records from.
228 * @param column The column in the where clause.
229 * @param value The value of the column.
230 * @throws TorqueException Any exceptions caught during processing will be
231 * rethrown wrapped into a TorqueException.
232 */
233 public static void deleteAll(
234 Connection con,
235 String table,
236 String column,
237 int value)
238 throws TorqueException
239 {
240 Statement statement = null;
241 try
242 {
243 statement = con.createStatement();
244
245 StringBuffer query = new StringBuffer();
246 query.append("DELETE FROM ")
247 .append(table)
248 .append(" WHERE ")
249 .append(column)
250 .append(" = ")
251 .append(value);
252
253 statement.executeUpdate(query.toString());
254 }
255 catch (SQLException e)
256 {
257 throw new TorqueException(e);
258 }
259 finally
260 {
261 if (statement != null)
262 {
263 try
264 {
265 statement.close();
266 }
267 catch (SQLException e)
268 {
269 throw new TorqueException(e);
270 }
271 }
272 }
273 }
274
275 /***
276 * Convenience method that uses straight JDBC to delete multiple
277 * rows. Village throws an Exception when multiple rows are
278 * deleted. This method attempts to get the default database from
279 * the pool.
280 *
281 * @param table The table to delete records from.
282 * @param column The column in the where clause.
283 * @param value The value of the column.
284 * @throws TorqueException Any exceptions caught during processing will be
285 * rethrown wrapped into a TorqueException.
286 */
287 public static void deleteAll(String table, String column, int value)
288 throws TorqueException
289 {
290 Connection con = null;
291 try
292 {
293
294 con = Torque.getConnection(Torque.getDefaultDB());
295 deleteAll(con, table, column, value);
296 }
297 finally
298 {
299 Torque.closeConnection(con);
300 }
301 }
302
303 /***
304 * Method to perform deletes based on values and keys in a
305 * Criteria.
306 *
307 * @param criteria The criteria to use.
308 * @throws TorqueException Any exceptions caught during processing will be
309 * rethrown wrapped into a TorqueException.
310 */
311 public static void doDelete(Criteria criteria) throws TorqueException
312 {
313 Connection con = null;
314 try
315 {
316 con = Transaction.beginOptional(
317 criteria.getDbName(),
318 criteria.isUseTransaction());
319 doDelete(criteria, con);
320 Transaction.commit(con);
321 }
322 catch (TorqueException e)
323 {
324 Transaction.safeRollback(con);
325 throw e;
326 }
327 }
328
329 /***
330 * Method to perform deletes based on values and keys in a Criteria.
331 *
332 * @param criteria The criteria to use.
333 * @param con A Connection.
334 * @throws TorqueException Any exceptions caught during processing will be
335 * rethrown wrapped into a TorqueException.
336 */
337 public static void doDelete(Criteria criteria, Connection con)
338 throws TorqueException
339 {
340 String dbName = criteria.getDbName();
341 final DatabaseMap dbMap = Torque.getDatabaseMap(dbName);
342
343
344
345
346
347 SQLBuilder.TableCallback tc = new SQLBuilder.TableCallback() {
348 public void process (Set tables, String key, Criteria crit)
349 {
350 if (crit.isCascade())
351 {
352
353 TableMap[] tableMaps = dbMap.getTables();
354 for (int i = 0; i < tableMaps.length; i++)
355 {
356 ColumnMap[] columnMaps = tableMaps[i].getColumns();
357
358 for (int j = 0; j < columnMaps.length; j++)
359 {
360
361
362
363 if (columnMaps[j].isForeignKey()
364 && columnMaps[j].isPrimaryKey()
365 && key.equals(columnMaps[j].getRelatedName()))
366 {
367 tables.add(tableMaps[i].getName());
368 crit.add(columnMaps[j].getFullyQualifiedName(),
369 crit.getValue(key));
370 }
371 }
372 }
373 }
374 }
375 };
376
377 Set tables = SQLBuilder.getTableSet(criteria, tc);
378
379 try
380 {
381 processTables(criteria, tables, con, new ProcessCallback() {
382 public void process(String table, String dbName, Record rec)
383 throws Exception
384 {
385 rec.markToBeDeleted();
386 rec.save();
387 }
388 });
389 }
390 catch (Exception e)
391 {
392 throwTorqueException(e);
393 }
394 }
395
396 /***
397 * Method to perform inserts based on values and keys in a
398 * Criteria.
399 * <p>
400 * If the primary key is auto incremented the data in Criteria
401 * will be inserted and the auto increment value will be returned.
402 * <p>
403 * If the primary key is included in Criteria then that value will
404 * be used to insert the row.
405 * <p>
406 * If no primary key is included in Criteria then we will try to
407 * figure out the primary key from the database map and insert the
408 * row with the next available id using util.db.IDBroker.
409 * <p>
410 * If no primary key is defined for the table the values will be
411 * inserted as specified in Criteria and -1 will be returned.
412 *
413 * @param criteria Object containing values to insert.
414 * @return An Object which is the id of the row that was inserted
415 * (if the table has a primary key) or null (if the table does not
416 * have a primary key).
417 * @throws TorqueException Any exceptions caught during processing will be
418 * rethrown wrapped into a TorqueException.
419 */
420 public static ObjectKey doInsert(Criteria criteria) throws TorqueException
421 {
422 Connection con = null;
423 ObjectKey id = null;
424
425 try
426 {
427 con = Transaction.beginOptional(
428 criteria.getDbName(),
429 criteria.isUseTransaction());
430 id = doInsert(criteria, con);
431 Transaction.commit(con);
432 }
433 catch (TorqueException e)
434 {
435 Transaction.safeRollback(con);
436 throw e;
437 }
438
439 return id;
440 }
441
442 /***
443 * Method to perform inserts based on values and keys in a
444 * Criteria.
445 * <p>
446 * If the primary key is auto incremented the data in Criteria
447 * will be inserted and the auto increment value will be returned.
448 * <p>
449 * If the primary key is included in Criteria then that value will
450 * be used to insert the row.
451 * <p>
452 * If no primary key is included in Criteria then we will try to
453 * figure out the primary key from the database map and insert the
454 * row with the next available id using util.db.IDBroker.
455 * <p>
456 * If no primary key is defined for the table the values will be
457 * inserted as specified in Criteria and null will be returned.
458 *
459 * @param criteria Object containing values to insert.
460 * @param con A Connection.
461 * @return An Object which is the id of the row that was inserted
462 * (if the table has a primary key) or null (if the table does not
463 * have a primary key).
464 * @throws TorqueException Any exceptions caught during processing will be
465 * rethrown wrapped into a TorqueException.
466 */
467 public static ObjectKey doInsert(Criteria criteria, Connection con)
468 throws TorqueException
469 {
470 SimpleKey id = null;
471
472
473
474 String table = null;
475 Iterator keys = criteria.keySet().iterator();
476 if (keys.hasNext())
477 {
478 table = criteria.getTableName((String) keys.next());
479 }
480 else
481 {
482 throw new TorqueException("Database insert attempted without "
483 + "anything specified to insert");
484 }
485
486 String dbName = criteria.getDbName();
487 Database database = Torque.getDatabase(dbName);
488 DatabaseMap dbMap = database.getDatabaseMap();
489 TableMap tableMap = dbMap.getTable(table);
490 Object keyInfo = tableMap.getPrimaryKeyMethodInfo();
491 IdGenerator keyGen
492 = database.getIdGenerator(tableMap.getPrimaryKeyMethod());
493
494 ColumnMap pk = getPrimaryKey(criteria);
495
496
497
498 if (keyGen != null && keyGen.isPriorToInsert())
499 {
500
501
502 if (pk != null && !criteria.containsKey(pk.getFullyQualifiedName()))
503 {
504 id = getId(pk, keyGen, con, keyInfo);
505 criteria.add(pk.getFullyQualifiedName(), id);
506 }
507 }
508
509
510 TableDataSet tds = null;
511 try
512 {
513 String tableName = SQLBuilder.getFullTableName(table, dbName);
514 tds = new TableDataSet(con, tableName);
515 Record rec = tds.addRecord();
516
517 BasePeer.insertOrUpdateRecord(rec, table, dbName, criteria);
518 }
519 catch (DataSetException e)
520 {
521 throwTorqueException(e);
522 }
523 catch (SQLException e)
524 {
525 throwTorqueException(e);
526 }
527 catch (TorqueException e)
528 {
529 throwTorqueException(e);
530 }
531 finally
532 {
533 VillageUtils.close(tds);
534 }
535
536
537
538 if (keyGen != null && keyGen.isPostInsert())
539 {
540 id = getId(pk, keyGen, con, keyInfo);
541 }
542
543 return id;
544 }
545
546 /***
547 * Create an Id for insertion in the Criteria
548 *
549 * @param pk ColumnMap for the Primary key
550 * @param keyGen The Id Generator object
551 * @param con The SQL Connection to run the id generation under
552 * @param keyInfo KeyInfo Parameter from the Table map
553 *
554 * @return A simple Key representing the new Id value
555 * @throws TorqueException Possible errors get wrapped in here.
556 */
557 private static SimpleKey getId(ColumnMap pk, IdGenerator keyGen, Connection con, Object keyInfo)
558 throws TorqueException
559 {
560 SimpleKey id = null;
561
562 try
563 {
564 if (pk != null && keyGen != null)
565 {
566 if (pk.getType() instanceof Number)
567 {
568 id = new NumberKey(
569 keyGen.getIdAsBigDecimal(con, keyInfo));
570 }
571 else
572 {
573 id = new StringKey(keyGen.getIdAsString(con, keyInfo));
574 }
575 }
576 }
577 catch (Exception e)
578 {
579 throwTorqueException(e);
580 }
581 return id;
582 }
583
584 /***
585 * Grouping of code used in both doInsert() and doUpdate()
586 * methods. Sets up a Record for saving.
587 *
588 * @param rec A Record.
589 * @param table Name of table.
590 * @param criteria A Criteria.
591 * @throws TorqueException Any exceptions caught during processing will be
592 * rethrown wrapped into a TorqueException.
593 */
594 private static void insertOrUpdateRecord(
595 Record rec,
596 String table,
597 String dbName,
598 Criteria criteria)
599 throws TorqueException
600 {
601 DatabaseMap dbMap = Torque.getDatabaseMap(dbName);
602
603 ColumnMap[] columnMaps = dbMap.getTable(table).getColumns();
604 boolean shouldSave = false;
605 for (int j = 0; j < columnMaps.length; j++)
606 {
607 ColumnMap colMap = columnMaps[j];
608 String colName = colMap.getColumnName();
609 String key = new StringBuffer(colMap.getTableName())
610 .append('.')
611 .append(colName)
612 .toString();
613 if (criteria.containsKey(key))
614 {
615 try
616 {
617 VillageUtils.setVillageValue(criteria, key, rec, colName);
618 shouldSave = true;
619 }
620 catch (Exception e)
621 {
622 throwTorqueException(e);
623 }
624 }
625 }
626
627 if (shouldSave)
628 {
629 try
630 {
631 rec.save();
632 }
633 catch (Exception e)
634 {
635 throwTorqueException(e);
636 }
637 }
638 else
639 {
640 throw new TorqueException("No changes to save");
641 }
642 }
643
644 /***
645 * Method to create an SQL query for display only based on values in a
646 * Criteria.
647 *
648 * @param criteria A Criteria.
649 * @return the SQL query for display
650 * @exception TorqueException Trouble creating the query string.
651 */
652 static String createQueryDisplayString(Criteria criteria)
653 throws TorqueException
654 {
655 return createQuery(criteria).toString();
656 }
657
658 /***
659 * Method to create an SQL query for actual execution based on values in a
660 * Criteria.
661 *
662 * @param criteria A Criteria.
663 * @return the SQL query for actual execution
664 * @exception TorqueException Trouble creating the query string.
665 */
666 public static String createQueryString(Criteria criteria)
667 throws TorqueException
668 {
669 Query query = createQuery(criteria);
670 return query.toString();
671 }
672
673 /***
674 * Method to create an SQL query based on values in a Criteria. Note that
675 * final manipulation of the limit and offset are performed when the query
676 * is actually executed.
677 *
678 * @param criteria A Criteria.
679 * @return the sql query
680 * @exception TorqueException Trouble creating the query string.
681 */
682 static Query createQuery(Criteria criteria)
683 throws TorqueException
684 {
685 return SQLBuilder.buildQueryClause(criteria, null, new SQLBuilder.QueryCallback() {
686 public String process(Criteria.Criterion criterion, List params)
687 {
688 return criterion.toString();
689 }
690 });
691 }
692
693 /***
694 * Returns all results.
695 *
696 * @param criteria A Criteria.
697 * @return List of Record objects.
698 * @throws TorqueException Any exceptions caught during processing will be
699 * rethrown wrapped into a TorqueException.
700 */
701 public static List doSelect(Criteria criteria) throws TorqueException
702 {
703 Connection con = null;
704 List results = null;
705
706 try
707 {
708 con = Transaction.beginOptional(
709 criteria.getDbName(),
710 criteria.isUseTransaction());
711 results = doSelect(criteria, con);
712 Transaction.commit(con);
713 }
714 catch (TorqueException e)
715 {
716 Transaction.safeRollback(con);
717 throw e;
718 }
719 return results;
720 }
721
722 /***
723 * Returns all results.
724 *
725 * @param criteria A Criteria.
726 * @param con A Connection.
727 * @return List of Record objects.
728 * @throws TorqueException Any exceptions caught during processing will be
729 * rethrown wrapped into a TorqueException.
730 */
731 public static List doSelect(Criteria criteria, Connection con)
732 throws TorqueException
733 {
734 Query query = createQuery(criteria);
735 DB dbadapter = Torque.getDB(criteria.getDbName());
736
737
738 return executeQuery(query.toString(),
739 dbadapter.supportsNativeOffset() ? 0 : criteria.getOffset(),
740 dbadapter.supportsNativeLimit() ? -1 : criteria.getLimit(),
741 criteria.isSingleRecord(),
742 con);
743 }
744
745 /***
746 * Utility method which executes a given sql statement. This
747 * method should be used for select statements only. Use
748 * executeStatement for update, insert, and delete operations.
749 *
750 * @param queryString A String with the sql statement to execute.
751 * @return List of Record objects.
752 * @throws TorqueException Any exceptions caught during processing will be
753 * rethrown wrapped into a TorqueException.
754 */
755 public static List executeQuery(String queryString) throws TorqueException
756 {
757 return executeQuery(queryString, Torque.getDefaultDB(), false);
758 }
759
760 /***
761 * Utility method which executes a given sql statement. This
762 * method should be used for select statements only. Use
763 * executeStatement for update, insert, and delete operations.
764 *
765 * @param queryString A String with the sql statement to execute.
766 * @param dbName The database to connect to.
767 * @return List of Record objects.
768 * @throws TorqueException Any exceptions caught during processing will be
769 * rethrown wrapped into a TorqueException.
770 */
771 public static List executeQuery(String queryString, String dbName)
772 throws TorqueException
773 {
774 return executeQuery(queryString, dbName, false);
775 }
776
777 /***
778 * Method for performing a SELECT. Returns all results.
779 *
780 * @param queryString A String with the sql statement to execute.
781 * @param dbName The database to connect to.
782 * @param singleRecord Whether or not we want to select only a
783 * single record.
784 * @return List of Record objects.
785 * @throws TorqueException Any exceptions caught during processing will be
786 * rethrown wrapped into a TorqueException.
787 */
788 public static List executeQuery(
789 String queryString,
790 String dbName,
791 boolean singleRecord)
792 throws TorqueException
793 {
794 return executeQuery(queryString, 0, -1, dbName, singleRecord);
795 }
796
797 /***
798 * Method for performing a SELECT. Returns all results.
799 *
800 * @param queryString A String with the sql statement to execute.
801 * @param singleRecord Whether or not we want to select only a
802 * single record.
803 * @param con A Connection.
804 * @return List of Record objects.
805 * @throws TorqueException Any exceptions caught during processing will be
806 * rethrown wrapped into a TorqueException.
807 */
808 public static List executeQuery(
809 String queryString,
810 boolean singleRecord,
811 Connection con)
812 throws TorqueException
813 {
814 return executeQuery(queryString, 0, -1, singleRecord, con);
815 }
816
817 /***
818 * Method for performing a SELECT.
819 *
820 * @param queryString A String with the sql statement to execute.
821 * @param start The first row to return.
822 * @param numberOfResults The number of rows to return.
823 * @param dbName The database to connect to.
824 * @param singleRecord Whether or not we want to select only a
825 * single record.
826 * @return List of Record objects.
827 * @throws TorqueException Any exceptions caught during processing will be
828 * rethrown wrapped into a TorqueException.
829 */
830 public static List executeQuery(
831 String queryString,
832 int start,
833 int numberOfResults,
834 String dbName,
835 boolean singleRecord)
836 throws TorqueException
837 {
838 Connection con = null;
839 List results = null;
840 try
841 {
842 con = Torque.getConnection(dbName);
843
844 results = executeQuery(
845 queryString,
846 start,
847 numberOfResults,
848 singleRecord,
849 con);
850 }
851 finally
852 {
853 Torque.closeConnection(con);
854 }
855 return results;
856 }
857
858 /***
859 * Method for performing a SELECT. Returns all results.
860 *
861 * @param queryString A String with the sql statement to execute.
862 * @param start The first row to return.
863 * @param numberOfResults The number of rows to return.
864 * @param singleRecord Whether or not we want to select only a
865 * single record.
866 * @param con A Connection.
867 * @return List of Record objects.
868 * @throws TorqueException Any exceptions caught during processing will be
869 * rethrown wrapped into a TorqueException.
870 */
871 public static List executeQuery(
872 String queryString,
873 int start,
874 int numberOfResults,
875 boolean singleRecord,
876 Connection con)
877 throws TorqueException
878 {
879 QueryDataSet qds = null;
880 List results = Collections.EMPTY_LIST;
881 try
882 {
883
884 long startTime = System.currentTimeMillis();
885 qds = new QueryDataSet(con, queryString);
886 if (log.isDebugEnabled())
887 {
888 log.debug("Elapsed time="
889 + (System.currentTimeMillis() - startTime) + " ms");
890 }
891 results = getSelectResults(
892 qds, start, numberOfResults, singleRecord);
893 }
894 catch (DataSetException e)
895 {
896 throwTorqueException(e);
897 }
898 catch (SQLException e)
899 {
900 throwTorqueException(e);
901 }
902 finally
903 {
904 VillageUtils.close(qds);
905 }
906 return results;
907 }
908
909 /***
910 * Returns all records in a QueryDataSet as a List of Record
911 * objects. Used for functionality like util.LargeSelect.
912 *
913 * @see #getSelectResults(QueryDataSet, int, int, boolean)
914 * @param qds the QueryDataSet
915 * @return a List of Record objects
916 * @throws TorqueException Any exceptions caught during processing will be
917 * rethrown wrapped into a TorqueException.
918 */
919 public static List getSelectResults(QueryDataSet qds)
920 throws TorqueException
921 {
922 return getSelectResults(qds, 0, -1, false);
923 }
924
925 /***
926 * Returns all records in a QueryDataSet as a List of Record
927 * objects. Used for functionality like util.LargeSelect.
928 *
929 * @see #getSelectResults(QueryDataSet, int, int, boolean)
930 * @param qds the QueryDataSet
931 * @param singleRecord
932 * @return a List of Record objects
933 * @throws TorqueException Any exceptions caught during processing will be
934 * rethrown wrapped into a TorqueException.
935 */
936 public static List getSelectResults(QueryDataSet qds, boolean singleRecord)
937 throws TorqueException
938 {
939 return getSelectResults(qds, 0, -1, singleRecord);
940 }
941
942 /***
943 * Returns numberOfResults records in a QueryDataSet as a List
944 * of Record objects. Starting at record 0. Used for
945 * functionality like util.LargeSelect.
946 *
947 * @see #getSelectResults(QueryDataSet, int, int, boolean)
948 * @param qds the QueryDataSet
949 * @param numberOfResults
950 * @param singleRecord
951 * @return a List of Record objects
952 * @throws TorqueException Any exceptions caught during processing will be
953 * rethrown wrapped into a TorqueException.
954 */
955 public static List getSelectResults(
956 QueryDataSet qds,
957 int numberOfResults,
958 boolean singleRecord)
959 throws TorqueException
960 {
961 List results = null;
962 if (numberOfResults != 0)
963 {
964 results = getSelectResults(qds, 0, numberOfResults, singleRecord);
965 }
966 return results;
967 }
968
969 /***
970 * Returns numberOfResults records in a QueryDataSet as a List
971 * of Record objects. Starting at record start. Used for
972 * functionality like util.LargeSelect.
973 *
974 * @param qds The <code>QueryDataSet</code> to extract results
975 * from.
976 * @param start The index from which to start retrieving
977 * <code>Record</code> objects from the data set.
978 * @param numberOfResults The number of results to return (or
979 * <code> -1</code> for all results).
980 * @param singleRecord Whether or not we want to select only a
981 * single record.
982 * @return A <code>List</code> of <code>Record</code> objects.
983 * @exception TorqueException If any <code>Exception</code> occurs.
984 */
985 public static List getSelectResults(
986 QueryDataSet qds,
987 int start,
988 int numberOfResults,
989 boolean singleRecord)
990 throws TorqueException
991 {
992 List results = null;
993 try
994 {
995 if (numberOfResults < 0)
996 {
997 results = new ArrayList();
998 qds.fetchRecords();
999 }
1000 else
1001 {
1002 results = new ArrayList(numberOfResults);
1003 qds.fetchRecords(start, numberOfResults);
1004 }
1005
1006 int startRecord = 0;
1007
1008
1009 if (start > 0 && numberOfResults <= 0)
1010 {
1011 startRecord = start;
1012 }
1013
1014
1015 for (int i = startRecord; i < qds.size(); i++)
1016 {
1017 Record rec = qds.getRecord(i);
1018 results.add(rec);
1019 }
1020
1021 if (results.size() > 1 && singleRecord)
1022 {
1023 handleMultipleRecords(qds);
1024 }
1025 }
1026 catch (Exception e)
1027 {
1028 throwTorqueException(e);
1029 }
1030 return results;
1031 }
1032
1033 /***
1034 * Helper method which returns the primary key contained
1035 * in the given Criteria object.
1036 *
1037 * @param criteria A Criteria.
1038 * @return ColumnMap if the Criteria object contains a primary
1039 * key, or null if it doesn't.
1040 * @throws TorqueException Any exceptions caught during processing will be
1041 * rethrown wrapped into a TorqueException.
1042 */
1043 private static ColumnMap getPrimaryKey(Criteria criteria)
1044 throws TorqueException
1045 {
1046
1047 String key = (String) criteria.keys().nextElement();
1048
1049 String table = criteria.getTableName(key);
1050 ColumnMap pk = null;
1051
1052 if (!table.equals(""))
1053 {
1054 DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());
1055 if (dbMap == null)
1056 {
1057 throw new TorqueException("dbMap is null");
1058 }
1059 if (dbMap.getTable(table) == null)
1060 {
1061 throw new TorqueException("dbMap.getTable() is null");
1062 }
1063
1064 ColumnMap[] columns = dbMap.getTable(table).getColumns();
1065
1066 for (int i = 0; i < columns.length; i++)
1067 {
1068 if (columns[i].isPrimaryKey())
1069 {
1070 pk = columns[i];
1071 break;
1072 }
1073 }
1074 }
1075 return pk;
1076 }
1077
1078 /***
1079 * Convenience method used to update rows in the DB. Checks if a
1080 * <i>single</i> int primary key is specified in the Criteria
1081 * object and uses it to perform the udpate. If no primary key is
1082 * specified an Exception will be thrown.
1083 * <p>
1084 * Use this method for performing an update of the kind:
1085 * <p>
1086 * "WHERE primary_key_id = an int"
1087 * <p>
1088 * To perform an update with non-primary key fields in the WHERE
1089 * clause use doUpdate(criteria, criteria).
1090 *
1091 * @param updateValues A Criteria object containing values used in
1092 * set clause.
1093 * @throws TorqueException Any exceptions caught during processing will be
1094 * rethrown wrapped into a TorqueException.
1095 */
1096 public static void doUpdate(Criteria updateValues) throws TorqueException
1097 {
1098 Connection con = null;
1099 try
1100 {
1101 con = Transaction.beginOptional(
1102 updateValues.getDbName(),
1103 updateValues.isUseTransaction());
1104 doUpdate(updateValues, con);
1105 Transaction.commit(con);
1106 }
1107 catch (TorqueException e)
1108 {
1109 Transaction.safeRollback(con);
1110 throw e;
1111 }
1112 }
1113
1114 /***
1115 * Convenience method used to update rows in the DB. Checks if a
1116 * <i>single</i> int primary key is specified in the Criteria
1117 * object and uses it to perform the udpate. If no primary key is
1118 * specified an Exception will be thrown.
1119 * <p>
1120 * Use this method for performing an update of the kind:
1121 * <p>
1122 * "WHERE primary_key_id = an int"
1123 * <p>
1124 * To perform an update with non-primary key fields in the WHERE
1125 * clause use doUpdate(criteria, criteria).
1126 *
1127 * @param updateValues A Criteria object containing values used in
1128 * set clause.
1129 * @param con A Connection.
1130 * @throws TorqueException Any exceptions caught during processing will be
1131 * rethrown wrapped into a TorqueException.
1132 */
1133 public static void doUpdate(Criteria updateValues, Connection con)
1134 throws TorqueException
1135 {
1136 ColumnMap pk = getPrimaryKey(updateValues);
1137 Criteria selectCriteria = null;
1138
1139 if (pk != null && updateValues.containsKey(pk.getFullyQualifiedName()))
1140 {
1141 selectCriteria = new Criteria(2);
1142 selectCriteria.put(pk.getFullyQualifiedName(),
1143 updateValues.remove(pk.getFullyQualifiedName()));
1144 }
1145 else
1146 {
1147 throw new TorqueException("No PK specified for database update");
1148 }
1149
1150 doUpdate(selectCriteria, updateValues, con);
1151 }
1152
1153 /***
1154 * Method used to update rows in the DB. Rows are selected based
1155 * on selectCriteria and updated using values in updateValues.
1156 * <p>
1157 * Use this method for performing an update of the kind:
1158 * <p>
1159 * WHERE some_column = some value AND could_have_another_column =
1160 * another value AND so on...
1161 *
1162 * @param selectCriteria A Criteria object containing values used in where
1163 * clause.
1164 * @param updateValues A Criteria object containing values used in set
1165 * clause.
1166 * @throws TorqueException Any exceptions caught during processing will be
1167 * rethrown wrapped into a TorqueException.
1168 */
1169 public static void doUpdate(Criteria selectCriteria, Criteria updateValues)
1170 throws TorqueException
1171 {
1172 Connection con = null;
1173 try
1174 {
1175 con = Transaction.beginOptional(
1176 selectCriteria.getDbName(),
1177 updateValues.isUseTransaction());
1178 doUpdate(selectCriteria, updateValues, con);
1179 Transaction.commit(con);
1180 }
1181 catch (TorqueException e)
1182 {
1183 Transaction.safeRollback(con);
1184 throw e;
1185 }
1186 }
1187
1188 /***
1189 * Method used to update rows in the DB. Rows are selected based
1190 * on criteria and updated using values in updateValues.
1191 * <p>
1192 * Use this method for performing an update of the kind:
1193 * <p>
1194 * WHERE some_column = some value AND could_have_another_column =
1195 * another value AND so on.
1196 *
1197 * @param criteria A Criteria object containing values used in where
1198 * clause.
1199 * @param updateValues A Criteria object containing values used in set
1200 * clause.
1201 * @param con A Connection.
1202 * @throws TorqueException Any exceptions caught during processing will be
1203 * rethrown wrapped into a TorqueException.
1204 */
1205 public static void doUpdate(
1206 Criteria criteria,
1207 final Criteria updateValues,
1208 Connection con)
1209 throws TorqueException
1210 {
1211 Set tables = SQLBuilder.getTableSet(criteria, null);
1212
1213 try
1214 {
1215 processTables(criteria, tables, con, new ProcessCallback() {
1216 public void process (String table, String dbName, Record rec)
1217 throws Exception
1218 {
1219
1220 BasePeer.insertOrUpdateRecord(rec, table, dbName, updateValues);
1221 }
1222 });
1223 }
1224 catch (Exception e)
1225 {
1226 throwTorqueException(e);
1227 }
1228 }
1229
1230 /***
1231 * Utility method which executes a given sql statement. This
1232 * method should be used for update, insert, and delete
1233 * statements. Use executeQuery() for selects.
1234 *
1235 * @param statementString A String with the sql statement to execute.
1236 * @return The number of rows affected.
1237 * @throws TorqueException Any exceptions caught during processing will be
1238 * rethrown wrapped into a TorqueException.
1239 */
1240 public static int executeStatement(String statementString) throws TorqueException
1241 {
1242 return executeStatement(statementString, Torque.getDefaultDB());
1243 }
1244
1245 /***
1246 * Utility method which executes a given sql statement. This
1247 * method should be used for update, insert, and delete
1248 * statements. Use executeQuery() for selects.
1249 *
1250 * @param statementString A String with the sql statement to execute.
1251 * @param dbName Name of database to connect to.
1252 * @return The number of rows affected.
1253 * @throws TorqueException Any exceptions caught during processing will be
1254 * rethrown wrapped into a TorqueException.
1255 */
1256 public static int executeStatement(String statementString, String dbName)
1257 throws TorqueException
1258 {
1259 Connection con = null;
1260 int rowCount = -1;
1261 try
1262 {
1263 con = Torque.getConnection(dbName);
1264 rowCount = executeStatement(statementString, con);
1265 }
1266 finally
1267 {
1268 Torque.closeConnection(con);
1269 }
1270 return rowCount;
1271 }
1272
1273 /***
1274 * Utility method which executes a given sql statement. This
1275 * method should be used for update, insert, and delete
1276 * statements. Use executeQuery() for selects.
1277 *
1278 * @param statementString A String with the sql statement to execute.
1279 * @param con A Connection.
1280 * @return The number of rows affected.
1281 * @throws TorqueException Any exceptions caught during processing will be
1282 * rethrown wrapped into a TorqueException.
1283 */
1284 public static int executeStatement(String statementString, Connection con)
1285 throws TorqueException
1286 {
1287 int rowCount = -1;
1288 Statement statement = null;
1289 try
1290 {
1291 statement = con.createStatement();
1292 rowCount = statement.executeUpdate(statementString);
1293 }
1294 catch (SQLException e)
1295 {
1296 throw new TorqueException(e);
1297 }
1298 finally
1299 {
1300 if (statement != null)
1301 {
1302 try
1303 {
1304 statement.close();
1305 }
1306 catch (SQLException e)
1307 {
1308 throw new TorqueException(e);
1309 }
1310 }
1311 }
1312 return rowCount;
1313 }
1314
1315 /***
1316 * If the user specified that (s)he only wants to retrieve a
1317 * single record and multiple records are retrieved, this method
1318 * is called to handle the situation. The default behavior is to
1319 * throw an exception, but subclasses can override this method as
1320 * needed.
1321 *
1322 * @param ds The DataSet which contains multiple records.
1323 * @exception TorqueException Couldn't handle multiple records.
1324 */
1325 protected static void handleMultipleRecords(DataSet ds)
1326 throws TorqueException
1327 {
1328 throw new TorqueException("Criteria expected single Record and "
1329 + "Multiple Records were selected");
1330 }
1331
1332 /***
1333 * This method returns the MapBuilder specified in the name
1334 * parameter. You should pass in the full path to the class, ie:
1335 * org.apache.torque.util.db.map.TurbineMapBuilder. The
1336 * MapBuilder instances are cached in this class for speed.
1337 *
1338 * @param name name of the MapBuilder
1339 * @return A MapBuilder, not null
1340 * @throws TorqueException if the Map Builder cannot be instantiated
1341 */
1342 public static MapBuilder getMapBuilder(String name)
1343 throws TorqueException
1344 {
1345 synchronized (mapBuilders)
1346 {
1347 try
1348 {
1349 MapBuilder mb = (MapBuilder) mapBuilders.get(name);
1350
1351 if (mb == null)
1352 {
1353 mb = (MapBuilder) Class.forName(name).newInstance();
1354
1355 mapBuilders.put(name, mb);
1356 }
1357
1358
1359
1360
1361
1362
1363
1364 if (mb.isBuilt())
1365 {
1366 return mb;
1367 }
1368
1369 try
1370 {
1371 mb.doBuild();
1372 }
1373 catch (Exception e)
1374 {
1375
1376
1377
1378 throw e;
1379 }
1380
1381 return mb;
1382 }
1383 catch (Exception e)
1384 {
1385 log.error("BasePeer.MapBuilder failed trying to instantiate: "
1386 + name, e);
1387 throw new TorqueException(e);
1388 }
1389 }
1390 }
1391
1392 /***
1393 * Performs a SQL <code>select</code> using a PreparedStatement.
1394 * Note: this method does not handle null criteria values.
1395 *
1396 * @param criteria
1397 * @param con
1398 * @return a List of Record objects.
1399 * @throws TorqueException Error performing database query.
1400 */
1401 public static List doPSSelect(Criteria criteria, Connection con)
1402 throws TorqueException
1403 {
1404 List v = null;
1405
1406 StringBuffer qry = new StringBuffer();
1407 List params = new ArrayList(criteria.size());
1408
1409 createPreparedStatement(criteria, qry, params);
1410
1411 PreparedStatement statement = null;
1412 try
1413 {
1414 statement = con.prepareStatement(qry.toString());
1415
1416 for (int i = 0; i < params.size(); i++)
1417 {
1418 Object param = params.get(i);
1419 if (param instanceof java.sql.Date)
1420 {
1421 statement.setDate(i + 1, (java.sql.Date) param);
1422 }
1423 else if (param instanceof NumberKey)
1424 {
1425 statement.setBigDecimal(i + 1,
1426 ((NumberKey) param).getBigDecimal());
1427 }
1428 else
1429 {
1430 statement.setString(i + 1, param.toString());
1431 }
1432 }
1433
1434 QueryDataSet qds = null;
1435 try
1436 {
1437 qds = new QueryDataSet(statement.executeQuery());
1438 v = getSelectResults(qds);
1439 }
1440 finally
1441 {
1442 VillageUtils.close(qds);
1443 }
1444 }
1445 catch (DataSetException e)
1446 {
1447 throwTorqueException(e);
1448 }
1449 catch (SQLException e)
1450 {
1451 throwTorqueException(e);
1452 }
1453 finally
1454 {
1455 if (statement != null)
1456 {
1457 try
1458 {
1459 statement.close();
1460 }
1461 catch (SQLException e)
1462 {
1463 throw new TorqueException(e);
1464 }
1465 }
1466 }
1467 return v;
1468 }
1469
1470 /***
1471 * Do a Prepared Statement select according to the given criteria
1472 *
1473 * @param criteria
1474 * @return a List of Record objects.
1475 * @throws TorqueException Any exceptions caught during processing will be
1476 * rethrown wrapped into a TorqueException.
1477 */
1478 public static List doPSSelect(Criteria criteria) throws TorqueException
1479 {
1480 Connection con = Torque.getConnection(criteria.getDbName());
1481 List v = null;
1482
1483 try
1484 {
1485 v = doPSSelect(criteria, con);
1486 }
1487 finally
1488 {
1489 Torque.closeConnection(con);
1490 }
1491 return v;
1492 }
1493
1494 /***
1495 * Create a new PreparedStatement. It builds a string representation
1496 * of a query and a list of PreparedStatement parameters.
1497 *
1498 * @param criteria
1499 * @param queryString
1500 * @param params
1501 * @throws TorqueException Any exceptions caught during processing will be
1502 * rethrown wrapped into a TorqueException.
1503 */
1504 public static void createPreparedStatement(
1505 Criteria criteria,
1506 StringBuffer queryString,
1507 List params)
1508 throws TorqueException
1509 {
1510 Query query = SQLBuilder.buildQueryClause(criteria, params, new SQLBuilder.QueryCallback() {
1511 public String process(Criteria.Criterion criterion, List params)
1512 {
1513 StringBuffer sb = new StringBuffer();
1514 criterion.appendPsTo(sb, params);
1515 return sb.toString();
1516 }
1517 });
1518
1519 String sql = query.toString();
1520 log.debug(sql);
1521
1522 queryString.append(sql);
1523 }
1524
1525 /***
1526 * Checks all columns in the criteria to see whether
1527 * booleanchar and booleanint columns are queried with a boolean.
1528 * If yes, the query values are mapped onto values the database
1529 * does understand, i.e. 0 and 1 for booleanints and N and Y for
1530 * booleanchar columns.
1531 *
1532 * @param criteria The criteria to be checked for booleanint and booleanchar
1533 * columns.
1534 * @param defaultTableMap the table map to be used if the table name is
1535 * not given in a column.
1536 * @throws TorqueException if the database map for the criteria cannot be
1537 * retrieved.
1538 */
1539 public static void correctBooleans(
1540 Criteria criteria,
1541 TableMap defaultTableMap)
1542 throws TorqueException
1543 {
1544 Iterator keyIt = criteria.keySet().iterator();
1545 while (keyIt.hasNext())
1546 {
1547 String key = (String) keyIt.next();
1548 String columnName;
1549 TableMap tableMap = null;
1550 int dotPosition = key.lastIndexOf(".");
1551 if (dotPosition == -1)
1552 {
1553 columnName = key;
1554 tableMap = defaultTableMap;
1555 }
1556 else
1557 {
1558 columnName = key.substring(dotPosition + 1);
1559 String tableName = key.substring(0, dotPosition);
1560 String databaseName = criteria.getDbName();
1561 if (databaseName == null)
1562 {
1563 databaseName = Torque.getDefaultDB();
1564 }
1565 DatabaseMap databaseMap = Torque.getDatabaseMap(databaseName);
1566 if (databaseMap != null)
1567 {
1568 tableMap = databaseMap.getTable(tableName);
1569 }
1570 if (tableMap == null)
1571 {
1572
1573 Map aliases = criteria.getAliases();
1574 if (aliases != null && aliases.get(tableName) != null)
1575 {
1576 tableName = (String) aliases.get(tableName);
1577 tableMap = databaseMap.getTable(tableName);
1578 }
1579 }
1580 if (tableMap == null)
1581 {
1582
1583 break;
1584 }
1585 }
1586
1587 ColumnMap columnMap = tableMap.getColumn(columnName);
1588 if (columnMap != null)
1589 {
1590 if ("BOOLEANINT".equals(columnMap.getTorqueType()))
1591 {
1592 Criteria.Criterion criterion = criteria.getCriterion(key);
1593 replaceBooleanValues(
1594 criterion,
1595 new Integer(1),
1596 new Integer(0));
1597 }
1598 else if ("BOOLEANCHAR".equals(columnMap.getTorqueType()))
1599 {
1600 Criteria.Criterion criterion = criteria.getCriterion(key);
1601 replaceBooleanValues(criterion, "Y", "N");
1602 }
1603 }
1604 }
1605 }
1606
1607 /***
1608 * Replaces any Boolean value in the criterion and its attached Criterions
1609 * by trueValue if the Boolean equals <code>Boolean.TRUE</code>
1610 * and falseValue if the Boolean equals <code>Boolean.FALSE</code>.
1611 *
1612 * @param criterion the criterion to replace Boolean values in.
1613 * @param trueValue the value by which Boolean.TRUE should be replaced.
1614 * @param falseValue the value by which Boolean.FALSE should be replaced.
1615 */
1616 private static void replaceBooleanValues(
1617 Criteria.Criterion criterion,
1618 Object trueValue,
1619 Object falseValue)
1620 {
1621
1622
1623 Criteria.Criterion[] attachedCriterions
1624 = criterion.getAttachedCriterion();
1625 for (int i = 0; i < attachedCriterions.length; ++i)
1626 {
1627 Object criterionValue
1628 = attachedCriterions[i].getValue();
1629 if (criterionValue instanceof Boolean)
1630 {
1631 Boolean booleanValue = (Boolean) criterionValue;
1632 attachedCriterions[i].setValue(
1633 Boolean.TRUE.equals(booleanValue)
1634 ? trueValue
1635 : falseValue);
1636 }
1637
1638 }
1639
1640 }
1641
1642 /***
1643 * Process the result of a Table list generation.
1644 * This runs the statements onto the list of tables and
1645 * provides a callback hook to add functionality.
1646 *
1647 * This method should've been in SQLBuilder, but is uses the handleMultipleRecords callback thingie..
1648 *
1649 * @param crit The criteria
1650 * @param tables A set of Tables to run on
1651 * @param con The SQL Connection to run the statements on
1652 * @param pc A ProcessCallback object
1653 *
1654 * @throws Exception An Error occured (should be wrapped into TorqueException)
1655 */
1656 private static void processTables(Criteria crit, Set tables, Connection con, ProcessCallback pc)
1657 throws Exception
1658 {
1659 String dbName = crit.getDbName();
1660 DB db = Torque.getDB(dbName);
1661 DatabaseMap dbMap = Torque.getDatabaseMap(dbName);
1662
1663
1664 for (Iterator it = tables.iterator(); it.hasNext();)
1665 {
1666 String table = (String) it.next();
1667 KeyDef kd = new KeyDef();
1668 Set whereClause = new HashSet();
1669
1670 ColumnMap[] columnMaps = dbMap.getTable(table).getColumns();
1671
1672 for (int j = 0; j < columnMaps.length; j++)
1673 {
1674 ColumnMap colMap = columnMaps[j];
1675 if (colMap.isPrimaryKey())
1676 {
1677 kd.addAttrib(colMap.getColumnName());
1678 }
1679
1680 String key = new StringBuffer(colMap.getTableName())
1681 .append('.')
1682 .append(colMap.getColumnName())
1683 .toString();
1684
1685 if (crit.containsKey(key))
1686 {
1687 if (crit
1688 .getComparison(key)
1689 .equals(Criteria.CUSTOM))
1690 {
1691 whereClause.add(crit.getString(key));
1692 }
1693 else
1694 {
1695 whereClause.add(
1696 SqlExpression.build(
1697 colMap.getColumnName(),
1698 crit.getValue(key),
1699 crit.getComparison(key),
1700 crit.isIgnoreCase(),
1701 db));
1702 }
1703 }
1704 }
1705
1706
1707 TableDataSet tds = null;
1708 try
1709 {
1710 String tableName = SQLBuilder.getFullTableName(table, dbName);
1711
1712
1713 tds = new TableDataSet(con, tableName, kd);
1714 String sqlSnippet = StringUtils.join(whereClause.iterator(), " AND ");
1715
1716 if (log.isDebugEnabled())
1717 {
1718 log.debug("BasePeer: whereClause=" + sqlSnippet);
1719 }
1720
1721 tds.where(sqlSnippet);
1722 tds.fetchRecords();
1723
1724 if (tds.size() > 1 && crit.isSingleRecord())
1725 {
1726 handleMultipleRecords(tds);
1727 }
1728
1729 for (int j = 0; j < tds.size(); j++)
1730 {
1731 Record rec = tds.getRecord(j);
1732
1733 if (pc != null)
1734 {
1735
1736 pc.process(table, dbName, rec);
1737 }
1738 }
1739 }
1740 finally
1741 {
1742 VillageUtils.close(tds);
1743 }
1744 }
1745 }
1746
1747 /***
1748 * Inner Interface that defines the Callback method for
1749 * the Record Processing
1750 */
1751 protected interface ProcessCallback
1752 {
1753 void process (String table, String dbName, Record rec)
1754 throws Exception;
1755 }
1756 }