View Javadoc

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