1 package org.apache.torque.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.Serializable;
23 import java.sql.Connection;
24 import java.sql.PreparedStatement;
25 import java.sql.SQLException;
26 import java.sql.Statement;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.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
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
340
341
342
343 SQLBuilder.TableCallback tc = new SQLBuilder.TableCallback() {
344 public void process (Set tables, String key, Criteria crit)
345 {
346 if (crit.isCascade())
347 {
348
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
357
358
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
469
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
493
494 if (keyGen != null && keyGen.isPriorToInsert())
495 {
496
497
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
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
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
533
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
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
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
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
1005 if (start > 0 && numberOfResults <= 0)
1006 {
1007 startRecord = start;
1008 }
1009
1010
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
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
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
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
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
1575
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
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
1660 TableDataSet tds = null;
1661 try
1662 {
1663 String tableName = SQLBuilder.getFullTableName(table, dbName);
1664
1665
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
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 }