001: /*
002: * This file or a portion of this file is licensed under the terms of
003: * the Globus Toolkit Public License, found in file GTPL, or at
004: * http://www.globus.org/toolkit/download/license.html. This notice must
005: * appear in redistributions of this file, with or without modification.
006: *
007: * Redistributions of this Software, with or without modification, must
008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
009: * some other similar material which is provided with the Software (if
010: * any).
011: *
012: * Copyright 1999-2004 University of Chicago and The University of
013: * Southern California. All rights reserved.
014: */
015: package org.griphyn.vdl.dbdriver;
016:
017: import java.lang.reflect.*;
018: import java.sql.*;
019: import java.util.*;
020: import java.io.PrintWriter;
021: import java.io.IOException;
022: import org.griphyn.vdl.util.ChimeraProperties;
023: import org.griphyn.common.util.DynamicLoader;
024: import org.griphyn.vdl.util.Logging;
025:
026: /**
027: * This common database interface that defines basic functionalities
028: * for interacting with backend SQL database. The implementation
029: * usually requires specific attention to the details between different
030: * databases, as each does things slightly different. The API provides
031: * a functionality that is independent of the schemas to be used.<p>
032: * The schema classes implement all their database access in terms of
033: * this database driver classes.<p>
034: * The separation of database driver and schema lowers the implementation
035: * cost, as only N driver and M schemas need to be implemented, instead
036: * of N x M schema-specific database-specific drivers.
037: *
038: * @author Jens-S. Vöckler
039: * @author Yong Zhao
040: * @version $Revision: 50 $
041: * @see org.griphyn.vdl.dbschema
042: */
043: public abstract class DatabaseDriver {
044: /**
045: * This variable keeps the JDBC handle for the database connection.
046: */
047: protected Connection m_connection = null;
048:
049: /**
050: * This list stores the prepared statements by their position.
051: * The constructor will initialize it. Explicit destruction can
052: * be requested per statement.
053: */
054: protected Map m_prepared;
055:
056: //
057: // class methods
058: //
059:
060: /**
061: * Instantiates the appropriate child according to property values.
062: * This method is a factory. Currently, drivers may be instantiated
063: * multiple times.<p>
064: *
065: * @param dbDriverName is the name of the class that conforms to
066: * the DatabaseDriver API. This class will be dynamically loaded.
067: * The passed value should not be <code>null</code>.
068: * @param propertyPrefix is the property prefix string to use.
069: * @param arguments are arguments to the constructor of the driver
070: * to load. Please use "new Object[0]" for the argumentless default
071: * constructor.
072: *
073: * @exception ClassNotFoundException if the driver for the database
074: * cannot be loaded. You might want to check your CLASSPATH, too.
075: * @exception NoSuchMethodException if the driver's constructor interface
076: * does not comply with the database driver API.
077: * @exception InstantiationException if the driver class is an abstract
078: * class instead of a concrete implementation.
079: * @exception IllegalAccessException if the constructor for the driver
080: * class it not publicly accessible to this package.
081: * @exception InvocationTargetException if the constructor of the driver
082: * throws an exception while being dynamically loaded.
083: * @exception SQLException if the driver for the database can be
084: * loaded, but faults when initially accessing the database
085: *
086: * @see org.griphyn.vdl.util.ChimeraProperties#getDatabaseDriverName
087: */
088: static public DatabaseDriver loadDriver(String dbDriverName,
089: String propertyPrefix, Object[] arguments)
090: throws ClassNotFoundException, IOException,
091: NoSuchMethodException, InstantiationException,
092: IllegalAccessException, InvocationTargetException,
093: SQLException {
094: Logging log = Logging.instance();
095: log.log("dbdriver", 3, "accessing loadDriver( "
096: + (dbDriverName == null ? "(null)" : dbDriverName)
097: + ", "
098: + (propertyPrefix == null ? "(null)" : propertyPrefix)
099: + " )");
100:
101: // determine the database driver to load
102: if (dbDriverName == null) {
103: dbDriverName = ChimeraProperties.instance()
104: .getDatabaseDriverName(propertyPrefix);
105: if (dbDriverName == null)
106: throw new RuntimeException("You need to specify the "
107: + propertyPrefix + " property");
108: }
109:
110: // syntactic sugar adds absolute class prefix
111: if (dbDriverName.indexOf('.') == -1) {
112: // how about xxx.getClass().getPackage().getName()?
113: dbDriverName = "org.griphyn.vdl.dbdriver." + dbDriverName;
114: }
115:
116: // POSTCONDITION: we have now a fully-qualified class name
117: log.log("dbdriver", 3, "trying to load " + dbDriverName);
118: DynamicLoader dl = new DynamicLoader(dbDriverName);
119: DatabaseDriver result = (DatabaseDriver) dl
120: .instantiate(arguments);
121:
122: // done
123: if (result == null)
124: log.log("dbdriver", 0, "unable to load " + dbDriverName);
125: else
126: log.log("dbdriver", 3, "successfully loaded "
127: + dbDriverName);
128: return result;
129: }
130:
131: /**
132: * Convenience method instantiates the appropriate child according to
133: * property values. Effectively, the following abbreviation is called:
134: *
135: * <pre>
136: * loadDriver( null, propertyPrefix, new Object[0] );
137: * </pre>
138: *
139: * @param propertyPrefix is the property prefix string to use.
140: *
141: * @exception ClassNotFoundException if the driver for the database
142: * cannot be loaded. You might want to check your CLASSPATH, too.
143: * @exception NoSuchMethodException if the driver's constructor interface
144: * does not comply with the database driver API.
145: * @exception InstantiationException if the driver class is an abstract
146: * class instead of a concrete implementation.
147: * @exception IllegalAccessException if the constructor for the driver
148: * class it not publicly accessible to this package.
149: * @exception InvocationTargetException if the constructor of the driver
150: * throws an exception while being dynamically loaded.
151: * @exception SQLException if the driver for the database can be
152: * loaded, but faults when initially accessing the database
153: *
154: * @see #loadDriver( String, String, Object[] )
155: */
156: static public DatabaseDriver loadDriver(String propertyPrefix)
157: throws ClassNotFoundException, IOException,
158: NoSuchMethodException, InstantiationException,
159: IllegalAccessException, InvocationTargetException,
160: SQLException {
161: return loadDriver(null, propertyPrefix, new Object[0]);
162: }
163:
164: /**
165: * Default constructor. As the constructor will do nothing, please use
166: * the connect method to obtain a database connection. This is the
167: * constructor that will be invoked when dynamically loading a driver.
168: *
169: * @see #connect( String, Properties, Set )
170: */
171: public DatabaseDriver() {
172: this .m_connection = null;
173: this .m_prepared = new TreeMap();
174: }
175:
176: /**
177: * Establishes a connection to the specified database. The parameters
178: * will often be ignored or abused for different purposes on different
179: * backends. It is assumed that the connection is not in auto-commit
180: * mode, and explicit commits must be issued.<p>
181: * Essentially, the deriving class will overwrite their connect method
182: * to fill in the appropriate driver, and otherwise just call this
183: * method.
184: *
185: * @param driver is the Java class name of the database driver package
186: * @param url the contact string to database, or schema location
187: * @param info additional parameters, usually username and password
188: * @param tables is a set of all table names in the schema. The
189: * existence of all tables will be checked to verify
190: * that the schema is active in the database.
191: * @return true if the connection succeeded, false otherwise. Usually,
192: * false is returned, if the any of the tables or sequences is missing.
193: *
194: * @see #connect( String, Properties, Set )
195: * @see org.griphyn.vdl.util.ChimeraProperties#getDatabaseDriverName
196: * @see org.griphyn.vdl.util.ChimeraProperties#getDatabaseURL
197: * @see org.griphyn.vdl.util.ChimeraProperties#getDatabaseDriverProperties
198: * @exception if the driver is incapable of establishing a connection.
199: */
200: protected boolean connect(String driver, String url,
201: Properties info, Set tables) throws SQLException,
202: ClassNotFoundException {
203: // load specificed driver class into memory
204: Class.forName(driver);
205:
206: Logging.instance().log("xaction", 1, "START connect to dbase");
207: m_connection = DriverManager.getConnection(url, info);
208: DriverManager.setLogWriter(new PrintWriter(System.err));
209: Logging.instance()
210: .log("xaction", 1, "FINAL connected to dbase");
211:
212: // determine that database version and driver version match
213: this .driverMatch();
214:
215: // disable auto commit, required for transaction (and speed).
216: Logging.instance().log("xaction", 1,
217: "START disable auto-commit");
218: m_connection.setAutoCommit(false);
219: Logging.instance().log("xaction", 1,
220: "FINAL disabled auto-commit");
221:
222: // auto-disconnect, should we forget it, or die in an orderly fashion
223: Runtime.getRuntime().addShutdownHook(new Thread() {
224: public void run() {
225: try {
226: disconnect();
227: } catch (SQLException e) {
228: e.printStackTrace();
229: }
230: }
231: });
232:
233: // final function return value
234: boolean result = true;
235:
236: /* ### disabled
237: // check for the presence of all tables in schema
238: if ( result && tables != null && tables.size() > 0 ) {
239: Logging.instance().log("xaction", 1, "START checking for tables" );
240:
241: List columns = new ArrayList();
242: columns.add( "tablename" );
243:
244: Map where = new HashMap();
245: where.put( "tableowner", info.get("username") );
246:
247: Set temp = new TreeSet();
248: ResultSet rs = this.select( columns, "pg_tables", where, null );
249: while ( rs.next() ) {
250: temp.add( rs.getString() );
251: }
252: result = temp.containsAll( tables );
253: Logging.instance().log("xaction", 1, "FINAL checking for tables" );
254: }
255: ### */
256:
257: return result;
258: }
259:
260: /**
261: * Establish a connection to your database. The parameters will often
262: * be ignored or abused for different purposes on different backends.
263: * It is assumed that the connection is not in auto-commit mode, and
264: * explicit commits must be issued.
265: *
266: * @param url the contact string to database, or schema location
267: * @param info additional parameters, usually username and password
268: * @param tables is a set of all table names in the schema. The
269: * existence of all tables will be checked to verify
270: * that the schema is active in the database.
271: * @return true if the connection succeeded, false otherwise. Usually,
272: * false is returned, if the any of the tables or sequences is missing.
273: * @exception if the driver is incapable of establishing a connection.
274: */
275: abstract public boolean connect(String url, Properties info,
276: Set tables) throws SQLException, ClassNotFoundException;
277:
278: /**
279: * Determines, if the JDBC driver is the right one for the database we
280: * talk to. Throws an exception if not.
281: */
282: public void driverMatch() throws SQLException {
283: // empty on purpose
284: }
285:
286: /**
287: * Close an established database connection.
288: *
289: * @exception if the driver threw up on the data.
290: */
291: public void disconnect() throws SQLException {
292: if (this .m_connection != null) {
293: this .m_connection.close();
294: this .m_connection = null;
295: }
296: }
297:
298: /**
299: * Closes an open connection to the database whenever this object
300: * is destroyed. This is still not foolproof.
301: *
302: */
303: protected void finalize() throws Throwable {
304: if (this .m_connection != null) {
305: this .m_connection.close();
306: this .m_connection = null;
307: }
308:
309: super .finalize();
310: }
311:
312: /**
313: * Determines, if the backend is expensive, and results should be cached.
314: * Ideally, this will move transparently into the backend itself.
315: * @return true if caching is advisable, false for no caching.
316: */
317: abstract public boolean cachingMakesSense();
318:
319: /**
320: * Quotes a string that may contain special SQL characters.
321: * @param s is the raw string.
322: * @return the quoted string, which may be just the input string.
323: */
324: public String quote(String s) {
325: // not implemented.
326: return s;
327: }
328:
329: /**
330: * Commits the latest changes to the database.
331: * @exception SQLException is propagated from the commit.
332: */
333: public void commit() throws SQLException {
334: this .m_connection.commit();
335: }
336:
337: /**
338: * Rolls back the latest changes to the database. Some databases may
339: * be incapable of rolling back.
340: * @exception SQLException is propagated from the rollback operation.
341: */
342: public void rollback() throws SQLException {
343: this .m_connection.rollback();
344: }
345:
346: /**
347: * Clears all warnings reported for this database driver. After a call
348: * to this method, the internal warnings are cleared until the next one
349: * occurs.
350: */
351: public void clearWarnings() throws SQLException {
352: this .m_connection.clearWarnings();
353: }
354:
355: /**
356: * Retrieves the first warning reported by calls on this Connection
357: * object.
358: * @return the first SQLWarning object or null if there are none
359: * @throws SQLException if a database access error occurs or this
360: * method is called on a closed connection
361: * @see java.sql.Connection#getWarnings
362: */
363: public SQLWarning getWarnings() throws SQLException {
364: return this .m_connection.getWarnings();
365: }
366:
367: /**
368: * Obtains the next value from a sequence. JDBC drivers which allow
369: * explicit access to sequence generator will return a valid value
370: * in this function. All other JDBC drivers should return -1.
371: *
372: * @param name is the name of the sequence.
373: * @return the next sequence number.
374: * @exception if something goes wrong while fetching the new value.
375: */
376: abstract public long sequence1(String name) throws SQLException;
377:
378: /**
379: * Obtains the sequence value for the current statement. JDBC driver
380: * that permit insertion of NULL into auto-increment value should use
381: * this method to return the inserted ID value via the statements
382: * getGeneratedKeys(). Other JDBC drivers should treat return the
383: * parametric id.
384: *
385: * @param s is a statment or prepared statement
386: * @param name is the name of the sequence.
387: * @param pos is the column number of the auto-increment column.
388: * @return the next sequence number.
389: * @exception if something goes wrong while fetching the new value.
390: */
391: abstract public long sequence2(Statement s, String name, int pos)
392: throws SQLException;
393:
394: /**
395: * Removes all rows that match the provided keyset from a table.
396: * @param table is the name of the table to remove values from
397: * @param columns is a set of column names and their associated
398: * values to select the removed columns. The map
399: * may be null to remove all rows in a table.
400: * @return the number of rows removed.
401: * @exception if something goes wrong while removing the values.
402: */
403: public int delete(String table, Map columns) throws SQLException {
404: StringBuffer request = new StringBuffer();
405:
406: request.append("DELETE FROM ").append(table);
407: if (columns != null && columns.size() > 0) {
408: request.append(" WHERE ");
409: for (Iterator i = columns.entrySet().iterator(); i
410: .hasNext();) {
411: Map.Entry me = (Map.Entry) i.next();
412: request.append((String) me.getKey()).append("='");
413: request.append(this .quote((String) me.getValue()))
414: .append('\'');
415: if (i.hasNext())
416: request.append(" AND ");
417: }
418: }
419:
420: String query = request.toString();
421: Logging.instance().log("sql", 2, query);
422: Logging.instance()
423: .log("xaction", 1, "START DELETE in " + table);
424: int count = m_connection.createStatement().executeUpdate(query);
425: Logging.instance().log("xaction", 1,
426: "FINAL DELETE in " + table + ": " + count);
427:
428: return count;
429: }
430:
431: /**
432: * Inserts a row in one given database table.
433: *
434: * @param table is the name of the table to insert into.
435: * @param keycolumns is a set of primary keys and their associated values.
436: * For special tables, the primary key set may be null
437: * or empty (e.g. a table without primary keys).
438: * @param columns is a set of regular keys and their associated values.
439: * @return the number of rows affected.
440: * @exception if something goes wrong while inserting the values.
441: */
442: public long insert(String table, Map keycolumns, Map columns)
443: throws SQLException {
444: StringBuffer request = new StringBuffer();
445: StringBuffer values = new StringBuffer();
446:
447: request.append("INSERT INTO ").append(table).append('(');
448: values.append('(');
449:
450: // conditionally add primary key columns, if they exist
451: if (keycolumns != null && keycolumns.size() > 0) {
452: for (Iterator i = keycolumns.entrySet().iterator(); i
453: .hasNext();) {
454: Map.Entry me = (Map.Entry) i.next();
455: request.append((String) me.getKey());
456: values.append(quote((String) me.getValue()));
457: if (i.hasNext()) {
458: request.append(", ");
459: values.append(", ");
460: }
461: }
462: }
463:
464: // conditionally add all columns
465: if (columns != null && columns.size() > 0) {
466: for (Iterator i = columns.entrySet().iterator(); i
467: .hasNext();) {
468: Map.Entry me = (Map.Entry) i.next();
469: request.append((String) me.getKey());
470: values.append(quote((String) me.getValue()));
471: if (i.hasNext()) {
472: request.append(", ");
473: values.append(", ");
474: }
475: }
476: }
477:
478: String query = request.toString() + " VALUES "
479: + values.toString();
480: Logging.instance().log("sql", 2, query);
481: Logging.instance()
482: .log("xaction", 1, "START INSERT in " + table);
483: Statement st = m_connection.createStatement();
484: long count = st.executeUpdate(query);
485: Logging.instance().log("xaction", 1,
486: "FINAL INSERT in " + table + ": " + count);
487: return count;
488: }
489:
490: /**
491: * Updates matching rows in one given database table.
492: * @param table is the name of the table to insert into.
493: * @param keycolumns is a set of primary keys and their associated values.
494: * For special tables, the primary key set may be null
495: * or empty (e.g. a table without primary keys).
496: * @param columns is a set of regular keys and their associated values.
497: * @return the number of rows affected
498: * @exception if something goes wrong while updating the values.
499: */
500: public int update(String table, Map keycolumns, Map columns)
501: throws SQLException {
502: StringBuffer request = new StringBuffer();
503:
504: request.append("UPDATE ").append(table).append(' ');
505:
506: // unconditionally add all columns
507: for (Iterator i = columns.entrySet().iterator(); i.hasNext();) {
508: Map.Entry me = (Map.Entry) i.next();
509: request.append("SET ").append((String) me.getKey()).append(
510: "='");
511: request.append(quote((String) me.getValue())).append('\'');
512: if (i.hasNext())
513: request.append(", ");
514: }
515:
516: // conditionally add primary key columns, if they exist
517: if (keycolumns != null && keycolumns.size() > 0) {
518: request.append(" WHERE ");
519: for (Iterator i = keycolumns.entrySet().iterator(); i
520: .hasNext();) {
521: Map.Entry me = (Map.Entry) i.next();
522: request.append((String) me.getKey()).append("='");
523: request.append(quote((String) me.getValue())).append(
524: '\'');
525: if (i.hasNext())
526: request.append(" AND ");
527: }
528: }
529:
530: // so far, so good
531: String query = request.toString();
532: Logging.instance().log("sql", 2, query);
533: Logging.instance()
534: .log("xaction", 1, "START UPDATE in " + table);
535: int count = m_connection.createStatement().executeUpdate(query);
536: Logging.instance().log("xaction", 1,
537: "FINAL UPDATE in " + table + ": " + count);
538: return count;
539: }
540:
541: /**
542: * Selects any rows in one or more colums from one or more tables
543: * restricted by some condition, possibly ordered.
544: * @param select is the ordered set of column names to select, or
545: * simply a one-value list with an asterisk.
546: * @param table is the name of the table to select from.
547: * @param where is a collection of column names and values they must equal.
548: * @param order is an optional ordering string.
549: */
550: public ResultSet select(List select, String table, Map where,
551: String order) throws SQLException {
552: StringBuffer request = new StringBuffer();
553:
554: request.append("SELECT ");
555: for (Iterator i = select.iterator(); i.hasNext();) {
556: request.append((String) i.next());
557: if (i.hasNext())
558: request.append(',');
559: }
560:
561: request.append(" FROM ").append(table);
562:
563: if (where != null && where.size() > 0) {
564: request.append(" WHERE ");
565: for (Iterator i = where.entrySet().iterator(); i.hasNext();) {
566: Map.Entry me = (Map.Entry) i.next();
567: request.append((String) me.getKey()).append("=\'");
568: request.append(quote((String) me.getValue())).append(
569: '\'');
570: if (i.hasNext())
571: request.append(" AND ");
572: }
573: }
574:
575: if (order != null && order.length() > 0)
576: request.append(order);
577:
578: String query = request.toString();
579: Logging.instance().log("sql", 2, query);
580: Logging.instance().log("xaction", 1,
581: "START SELECT FROM " + table);
582: ResultSet result = m_connection.createStatement().executeQuery(
583: query);
584: Logging.instance().log("xaction", 1,
585: "FINAL SELECT FROM " + table);
586: return result;
587: }
588:
589: /**
590: * Selects any rows in one or more colums from one or more tables
591: * restricted by some condition that allows operators. Permissable
592: * operators include =, <>, >, >=, <, <=, like, etc.
593: * possibly ordered.
594: *
595: * @param select is the ordered set of column names to select, or
596: * simply a one-value list with an asterisk.
597: * @param table is the name of the table to select from.
598: * @param where is a collection of column names and values
599: * @param operator is a collection of column names and operators
600: * if no entry is found for the name, then use '=' as default
601: * @param order is an optional ordering string.
602: */
603: public ResultSet select(List select, String table, Map where,
604: Map operator, String order) throws SQLException {
605: StringBuffer request = new StringBuffer();
606: Logging l = Logging.instance();
607:
608: request.append("SELECT ");
609: for (Iterator i = select.iterator(); i.hasNext();) {
610: request.append((String) i.next());
611: if (i.hasNext())
612: request.append(',');
613: }
614:
615: request.append(" FROM ").append(table);
616:
617: if (where != null && where.size() > 0) {
618: l.log("xaction", 1, "adding WHERE clause");
619: request.append(" WHERE ");
620: for (Iterator i = where.keySet().iterator(); i.hasNext();) {
621: String key = (String) i.next();
622: request.append(key);
623: String op = (String) operator.get(key);
624: request.append(op == null ? "=\'" : (" " + op + " \'"));
625: request.append(quote((String) where.get(key))).append(
626: '\'');
627: if (i.hasNext())
628: request.append(" AND ");
629: }
630: } else {
631: l.log("xaction", 1, "no WHERE clause");
632: }
633:
634: if (order != null && order.length() > 0)
635: request.append(order);
636:
637: String query = request.toString();
638: l.log("sql", 2, query);
639: l.log("xaction", 1, "START " + query);
640: ResultSet result = m_connection.createStatement().executeQuery(
641: query);
642: l.log("xaction", 1, "FINAL " + query);
643: return result;
644: }
645:
646: /**
647: * Drills a hole into the nice database driver abstraction to the JDBC3
648: * level. Use with caution.
649: * @param query is an SQL query statement.
650: * @exception SQLException if something goes wrong during the query
651: */
652: public ResultSet backdoor(String query) throws SQLException {
653: Logging.instance().log("sql", 2, query);
654: Logging.instance().log("xaction", 1, "START " + query);
655: ResultSet result = m_connection.createStatement().executeQuery(
656: query);
657: Logging.instance().log("xaction", 1, "FINAL " + query);
658: return result;
659: }
660:
661: //
662: // handle prepared statements
663: //
664:
665: /**
666: * Predicate to tell the schema, if using a string instead of number
667: * will result in the speedier index scans instead of sequential scans.
668: * PostGreSQL has this problem, but using strings in the place of
669: * integers may not be universally portable.
670: *
671: * @return true, if using strings instead of integers and bigints
672: * will yield better performance.
673: *
674: */
675: abstract public boolean preferString();
676:
677: /**
678: * Inserts a new prepared statement into the list of prepared
679: * statements. If the id is already taken, it will be
680: * rejected.
681: *
682: * @param id is the id into which to parse the statement
683: * @param statement is the before-parsing statement string
684: * @return true, if the statement was parsed and added at the id,
685: * false, if the id was already taken. The statement is not parsed
686: * in that case.
687: *
688: * @exception SQLException may be thrown by parsing the statement.
689: *
690: * @see #removePreparedStatement( String )
691: * @see #getPreparedStatement( String )
692: * @see #cancelPreparedStatement( String )
693: */
694: public boolean addPreparedStatement(String id, String statement)
695: throws SQLException {
696: // sanity check
697: if (this .m_prepared == null)
698: throw new RuntimeException(
699: "You forgot to initialize the prepared statement length");
700:
701: // duplicate check
702: if (this .m_prepared.containsKey(id))
703: return false;
704:
705: // add to id
706: Logging.instance().log("sql", 2, statement);
707: Logging.instance().log("xaction", 1,
708: "START prepare " + statement);
709:
710: try {
711: this .m_prepared.put(id, this .m_connection
712: .prepareStatement(statement));
713: } catch (SQLException original) {
714: SQLException sql = original;
715: for (int i = 0; sql != null; ++i) {
716: Logging.instance().log(
717: "sql",
718: 0,
719: "SQL error " + i + ": " + sql.getErrorCode()
720: + ": " + sql.getMessage());
721: sql = sql.getNextException();
722: }
723: throw original;
724: } catch (NullPointerException e) {
725: Logging.instance().log("sql", 0, "stumbled over null");
726: e.printStackTrace();
727: System.exit(1);
728: }
729:
730: Logging.instance().log("xaction", 1,
731: "FINAL prepare " + statement);
732: return true;
733: }
734:
735: /**
736: * Inserts a new prepared statement into the list of prepared
737: * statements. If the id is already taken, it will be
738: * rejected. This method can only be used with JDBC drivers
739: * that support auto-increment columns. It might fail with JDBC
740: * drivers that do not support auto-increment columns, depending
741: * on the driver's implementation.
742: *
743: * @param id is the id into which to parse the statement
744: * @param statement is the before-parsing statement string
745: * @param autoGeneratedKeys is true, if the statement should reserve
746: * space to return autoinc columns, false, if the statement does not
747: * have any such keys.
748: * @return true, if the statement was parsed and added at the id,
749: * false, if the id was already taken. The statement is not parsed
750: * in that case.
751: *
752: * @exception SQLException may be thrown by parsing the statement.
753: *
754: * @see #removePreparedStatement( String )
755: * @see #getPreparedStatement( String )
756: * @see #cancelPreparedStatement( String )
757: */
758: protected boolean addPreparedStatement(String id, String statement,
759: boolean autoGeneratedKeys) throws SQLException {
760: // sanity check
761: if (this .m_prepared == null)
762: throw new RuntimeException(
763: "You forgot to initialize the prepared statement length");
764:
765: // duplicate check
766: if (this .m_prepared.containsKey(id))
767: return false;
768:
769: // add to id
770: Logging.instance().log("sql", 2, statement);
771: Logging.instance().log("xaction", 1,
772: "START prepare " + statement);
773:
774: try {
775: this .m_prepared.put(id, this .m_connection.prepareStatement(
776: statement,
777: autoGeneratedKeys ? Statement.RETURN_GENERATED_KEYS
778: : Statement.NO_GENERATED_KEYS));
779: } catch (SQLException original) {
780: SQLException sql = original;
781: for (int i = 0; sql != null; ++i) {
782: Logging.instance().log(
783: "sql",
784: 0,
785: "SQL error " + i + ": " + sql.getErrorCode()
786: + ": " + sql.getMessage());
787: sql = sql.getNextException();
788: }
789: throw original;
790: } catch (NullPointerException e) {
791: Logging.instance().log("sql", 0, "stumbled over null");
792: e.printStackTrace();
793: System.exit(1);
794: }
795:
796: Logging.instance().log("xaction", 1,
797: "FINAL prepare " + statement);
798: return true;
799: }
800:
801: /**
802: * Inserts a new prepared statement into the list of prepared
803: * statements. If the id is already taken, an error will be
804: * printed and execution aborted.
805: *
806: * @param id is the id into which to parse the statement
807: * @param statement is the before-parsing statement string
808: * @return true, if the statement was parsed and added at the id,
809: * false, if the id was already taken. The statement is not parsed
810: * in that case.
811: *
812: * @exception SQLException may be thrown by parsing the statement.
813: *
814: * @see #addPreparedStatement( String, String )
815: * @see #getPreparedStatement( String )
816: * @see #cancelPreparedStatement( String )
817: */
818: public boolean insertPreparedStatement(String id, String statement)
819: throws SQLException {
820: boolean result = addPreparedStatement(id, statement);
821: if (result == false) {
822: System.err.println("Duplicate key " + id);
823: System.exit(1);
824: }
825: return result;
826: }
827:
828: /**
829: * Obtains a reference to a prepared statement to be used from
830: * the caller. This function will also reset the input values
831: * in the prepared statement.
832: *
833: * @param id is the place of the statement to free up.
834: * @exception SQLException if the database does not like the disconnect.
835: *
836: * @see java.sql.PreparedStatement#clearParameters()
837: * @see #addPreparedStatement( String, String )
838: * @see #removePreparedStatement( String )
839: * @see #cancelPreparedStatement( String )
840: */
841: public PreparedStatement getPreparedStatement(String id)
842: throws SQLException {
843: PreparedStatement result = (PreparedStatement) this .m_prepared
844: .get(id);
845: if (result != null)
846: result.clearParameters();
847: else
848: throw new SQLException("unknown prepared statement " + id);
849: return result;
850: }
851:
852: /**
853: * Explicitely requests a prepared id to be destroyed and its resources
854: * freed. Multiple invocation for the same id are harmless.
855: *
856: * @param id is the place of the statement to free up.
857: * @exception SQLException if the database does not like the disconnect.
858: *
859: * @see #addPreparedStatement( String, String )
860: * @see #getPreparedStatement( String )
861: * @see #cancelPreparedStatement( String )
862: */
863: public void removePreparedStatement(String id) throws SQLException {
864: PreparedStatement ps = (PreparedStatement) this .m_prepared
865: .remove(id);
866: if (ps != null)
867: ps.close();
868: }
869:
870: /**
871: * Cancels and resets all previous values of a prepared statement.
872: *
873: * @param id is the id for which to obtain the previously
874: * prepared statement.
875: *
876: * @exception SQLException if a database access error occurs while
877: * clearing the parameters.
878: * @exception ArrayIndexOutOfBoundsException if a non-existing id
879: * is being requested.
880: *
881: * @see java.sql.PreparedStatement#clearParameters()
882: * @see #addPreparedStatement( String, String )
883: * @see #getPreparedStatement( String )
884: * @see #removePreparedStatement( String )
885: */
886: public void cancelPreparedStatement(String id) throws SQLException,
887: ArrayIndexOutOfBoundsException {
888: PreparedStatement ps = (PreparedStatement) this.m_prepared
889: .get(id);
890: if (ps != null) {
891: ps.cancel();
892: ps.clearParameters();
893: }
894: }
895: }
|