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.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             // Get a connection to the db.
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         // This Callback adds all tables to the Table set which
344         // are referenced from a cascading criteria. As a result, all
345         // data that is referenced through foreign keys will also be
346         // deleted.
347         SQLBuilder.TableCallback tc = new SQLBuilder.TableCallback() {
348                 public void process (Set tables, String key, Criteria crit)
349                 {
350                     if (crit.isCascade())
351                     {
352                         // This steps thru all the columns in the database.
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                                 // Only delete rows where the foreign key is
361                                 // also a primary key.  Other rows need
362                                 // updating, but that is not implemented.
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         // Get the table name and method for determining the primary
473         // key value.
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         // If the keyMethod is SEQUENCE or IDBROKERTABLE, get the id
497         // before the insert.
498         if (keyGen != null && keyGen.isPriorToInsert())
499         {
500             // pk will be null if there is no primary key defined for the table
501             // we're inserting into.
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         // Use Village to perform the insert.
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             // not the fully qualified name, insertOrUpdateRecord wants to use table as an index...
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         // If the primary key column is auto-incremented, get the id
537         // now.
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         // Call Village depending on the capabilities of the DB
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             // execute the query
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             // execute the query
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             //Offset the correct number of records
1009             if (start > 0 && numberOfResults <= 0)
1010             {
1011                 startRecord = start;
1012             }
1013 
1014             // Return a List of Record objects.
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         // Assume all the keys are for the same table.
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                         // Callback must be called with table name without Schema!
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                     // Cache the MapBuilder before it is built.
1355                     mapBuilders.put(name, mb);
1356                 }
1357 
1358                 // Build the MapBuilder in its own synchronized block to
1359                 //  avoid locking up the whole Hashtable while doing so.
1360                 // Note that *all* threads need to do a sync check on isBuilt()
1361                 //  to avoid grabing an uninitialized MapBuilder. This, however,
1362                 //  is a relatively fast operation.
1363 
1364                 if (mb.isBuilt())
1365                 {
1366                     return mb;
1367                 }
1368 
1369                 try
1370                 {
1371                     mb.doBuild();
1372                 }
1373                 catch (Exception e)
1374                 {
1375                     // need to think about whether we'd want to remove
1376                     //  the MapBuilder from the cache if it can't be
1377                     //  built correctly...?  pgo
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                     // try aliases
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                     // no description of table available, do not modify anything
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         // attachedCriterions also contains the criterion itself,
1622         // so no additional treatment is needed for the criterion itself.
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         // create the statements for the tables
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             // Execute the statement for each table
1707             TableDataSet tds = null;
1708             try
1709             {
1710                 String tableName = SQLBuilder.getFullTableName(table, dbName);
1711 
1712                 // Get affected records.
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                         // Table name _without_ schema!
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 }