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.dbschema;
016:
017: import java.sql.*;
018: import java.io.File;
019: import java.io.IOException;
020: import java.util.*;
021: import java.lang.reflect.*;
022: import org.griphyn.vdl.util.ChimeraProperties;
023: import org.griphyn.common.util.DynamicLoader;
024: import org.griphyn.vdl.classes.Definitions;
025: import org.griphyn.vdl.dbdriver.*;
026: import org.griphyn.vdl.util.Logging;
027:
028: /**
029: * This common schema interface defines the schemas in which the
030: * abstraction layers access any given database. It is independent
031: * of the implementing database, and does so by going via the
032: * database driver class API.<p>
033: * The separation of database driver and schema lowers the implementation
034: * cost, as only N driver and M schemas need to be implemented, instead
035: * of N x M schema-specific database-specific drivers.
036: *
037: * @author Jens-S. Vöckler
038: * @author Yong Zhao
039: * @version $Revision: 365 $
040: * @see org.griphyn.vdl.dbdriver
041: */
042: public abstract class DatabaseSchema implements Catalog {
043: /**
044: * This is the variable that connect to the lower level database driver.
045: */
046: protected DatabaseDriver m_dbdriver;
047:
048: /**
049: * This stores properties specific to the schema. Currently unused.
050: */
051: protected Properties m_dbschemaprops;
052:
053: //
054: // class methods
055: //
056:
057: /**
058: * Instantiates the appropriate leaf schema according to property values.
059: * This method is a factory.
060: *
061: * @param dbSchemaName is the name of the class that conforms to
062: * the DatabaseSchema API. This class will be dynamically loaded.
063: * If the passed value is <code>null</code>, which should be the
064: * default, the value of property vds.db.schema is taken.
065: * @param propertyPrefix is the property prefix string to use.
066: * @param arguments are arguments to the constructor of the driver
067: * to load. Please use "new Object[0]" for the default constructor.
068: *
069: * @exception ClassNotFoundException if the schema for the database
070: * cannot be loaded. You might want to check your CLASSPATH, too.
071: * @exception NoSuchMethodException if the schema's constructor interface
072: * does not comply with the database driver API.
073: * @exception InstantiationException if the schema class is an abstract
074: * class instead of a concrete implementation.
075: * @exception IllegalAccessException if the constructor for the schema
076: * class it not publicly accessible to this package.
077: * @exception InvocationTargetException if the constructor of the schema
078: * throws an exception while being dynamically loaded.
079: *
080: * @see org.griphyn.vdl.util.ChimeraProperties
081: */
082: static public DatabaseSchema loadSchema(String dbSchemaName,
083: String propertyPrefix, Object[] arguments)
084: throws ClassNotFoundException, IOException,
085: NoSuchMethodException, InstantiationException,
086: IllegalAccessException, InvocationTargetException {
087: Logging log = Logging.instance();
088: log.log("dbschema", 3, "accessing loadSchema( "
089: + (dbSchemaName == null ? "(null)" : dbSchemaName)
090: + ", "
091: + (propertyPrefix == null ? "(null)" : propertyPrefix)
092: + " )");
093:
094: // determine the database schema to load
095: if (dbSchemaName == null) {
096: // get it by property prefix
097: dbSchemaName = ChimeraProperties.instance()
098: .getDatabaseSchemaName(propertyPrefix);
099: if (dbSchemaName == null)
100: throw new RuntimeException("You need to specify the "
101: + propertyPrefix + " property");
102: }
103:
104: // syntactic sugar adds absolute class prefix
105: if (dbSchemaName.indexOf('.') == -1) {
106: // how about xxx.getClass().getPackage().getName()?
107: dbSchemaName = "org.griphyn.vdl.dbschema." + dbSchemaName;
108: }
109:
110: // POSTCONDITION: we have now a fully-qualified class name
111: log.log("dbschema", 3, "trying to load " + dbSchemaName);
112: DynamicLoader dl = new DynamicLoader(dbSchemaName);
113: DatabaseSchema result = (DatabaseSchema) dl
114: .instantiate(arguments);
115:
116: // done
117: if (result == null)
118: log.log("dbschema", 0, "unable to load " + dbSchemaName);
119: else
120: log.log("dbschema", 3, "successfully loaded "
121: + dbSchemaName);
122: return result;
123: }
124:
125: /**
126: * Convenience method instantiates the appropriate child according to
127: * property values. Effectively, the following is being called:
128: *
129: * <pre>
130: * loadSchema( null, propertyPrefix, new Object[0] );
131: * </pre>
132: *
133: * @param propertyPrefix is the property prefix string to use.
134: *
135: * @exception ClassNotFoundException if the schema for the database
136: * cannot be loaded. You might want to check your CLASSPATH, too.
137: * @exception NoSuchMethodException if the schema's constructor interface
138: * does not comply with the database driver API.
139: * @exception InstantiationException if the schema class is an abstract
140: * class instead of a concrete implementation.
141: * @exception IllegalAccessException if the constructor for the schema
142: * class it not publicly accessible to this package.
143: * @exception InvocationTargetException if the constructor of the schema
144: * throws an exception while being dynamically loaded.
145: *
146: * @see #loadSchema( String, String, Object[] )
147: * @see org.griphyn.vdl.util.ChimeraProperties
148: */
149: static public DatabaseSchema loadSchema(String propertyPrefix)
150: throws ClassNotFoundException, IOException,
151: NoSuchMethodException, InstantiationException,
152: IllegalAccessException, InvocationTargetException {
153: return loadSchema(null, propertyPrefix, new Object[0]);
154: }
155:
156: //
157: // instance methods
158: //
159:
160: /**
161: * Minimalistic default ctor. This constructor does nothing,
162: * and loads nothing. But it initializes the empty schema props.
163: */
164: protected DatabaseSchema() {
165: Logging.instance().log("dbschema", 3,
166: "accessing DatabaseSchema()");
167: this .m_dbdriver = null;
168: this .m_dbschemaprops = new Properties();
169: }
170:
171: /**
172: * Connects to the database, this method does not rely on global
173: * property values, instead, each property has to be provided
174: * explicitly.
175: *
176: * @param dbDriverName is the name of the class that conforms to
177: * the DatabaseDriver API. This class will be dynamically loaded.
178: * @param url is the database url
179: * @param dbDriverProperties holds properties specific to the
180: * database driver.
181: * @param dbSchemaProperties holds properties specific to the
182: * database schema.
183: *
184: * @exception ClassNotFoundException if the driver for the database
185: * cannot be loaded. You might want to check your CLASSPATH, too.
186: * @exception NoSuchMethodException if the driver's constructor interface
187: * does not comply with the database driver API.
188: * @exception InstantiationException if the driver class is an abstract
189: * class instead of a concrete implementation.
190: * @exception IllegalAccessException if the constructor for the driver
191: * class it not publicly accessible to this package.
192: * @exception InvocationTargetException if the constructor of the driver
193: * throws an exception while being dynamically loaded.
194: * @exception SQLException if the driver for the database can be
195: * loaded, but faults when initially accessing the database
196: */
197: public DatabaseSchema(String dbDriverName, String url,
198: Properties dbDriverProperties, Properties dbSchemaProperties)
199: throws ClassNotFoundException, IOException,
200: NoSuchMethodException, InstantiationException,
201: IllegalAccessException, InvocationTargetException,
202: SQLException {
203: Logging
204: .instance()
205: .log("dbschema", 3,
206: "accessing DatabaseSchema(String,String, Properties, Properties)");
207:
208: // dynamically load the driver from its default constructor
209: this .m_dbdriver = DatabaseDriver.loadDriver(dbDriverName, null,
210: new Object[0]);
211: this .m_dbschemaprops = dbSchemaProperties;
212:
213: // create a database connection right now, right here
214: // mind, url may be null, which may be legal for some drivers!
215: Logging.instance().log("dbschema", 3,
216: "invoking connect( " + url + " )");
217: this .m_dbdriver.connect(url, dbDriverProperties, null);
218:
219: Logging.instance().log("dbschema", 3,
220: "connected to database backend");
221:
222: // prepare statements as necessary in the implementing classes!
223: }
224:
225: /**
226: * Guesses from the schema prefix the driver prefix.
227: *
228: * @param schemaPrefix is the property key prefix for the schema.
229: * @return the guess for the driver's prefix, may be <code>null</code>
230: */
231: private static String driverFromSchema(String schemaPrefix) {
232: String result = null;
233: if (schemaPrefix != null && schemaPrefix.endsWith(".schema"))
234: result = schemaPrefix.substring(0,
235: schemaPrefix.length() - 7)
236: + ".driver";
237: Logging.instance().log(
238: "dbschema",
239: 4,
240: "dbdriver prefix guess "
241: + (result == null ? "(null)" : result));
242: return result;
243: }
244:
245: /**
246: * Guesses from the schema prefix the db prefix.
247: *
248: * @param schemaPrefix is the property key prefix for the schema.
249: *
250: * @return the guess for the db properties prefix, may be <code>null</code>
251: */
252: private static String dbFromSchema(String schemaPrefix) {
253: String result = null;
254: if (schemaPrefix != null && schemaPrefix.endsWith(".schema"))
255: result = schemaPrefix.substring(0,
256: schemaPrefix.length() - 7);
257: Logging.instance().log(
258: "dbschema",
259: 4,
260: "db propertiesr prefix guess "
261: + (result == null ? "(null)" : result));
262: return result;
263: }
264:
265: /**
266: * Connects to the database as specified by the properties, and
267: * checks the schema implementation. Makes heavy use of global
268: * property values.
269: *
270: * @param dbDriverName is the name of the class that conforms to
271: * the DatabaseDriver API. This class will be dynamically loaded.
272: * If the passed value is <code>null</code>, which should be the
273: * default, the value of property vds.db.*.driver is taken.
274: * @param propertyPrefix is the property prefix string to use.
275: *
276: * @exception ClassNotFoundException if the driver for the database
277: * cannot be loaded. You might want to check your CLASSPATH, too.
278: * @exception NoSuchMethodException if the driver's constructor interface
279: * does not comply with the database driver API.
280: * @exception InstantiationException if the driver class is an abstract
281: * class instead of a concrete implementation.
282: * @exception IllegalAccessException if the constructor for the driver
283: * class it not publicly accessible to this package.
284: * @exception InvocationTargetException if the constructor of the driver
285: * throws an exception while being dynamically loaded.
286: * @exception SQLException if the driver for the database can be
287: * loaded, but faults when initially accessing the database
288: */
289: public DatabaseSchema(String dbDriverName, String propertyPrefix)
290: throws ClassNotFoundException, IOException,
291: NoSuchMethodException, InstantiationException,
292: IllegalAccessException, InvocationTargetException,
293: SQLException {
294: Logging.instance().log("dbschema", 3,
295: "accessing DatabaseSchema(String,String)");
296:
297: // guess the db driver property prefix from schema prefix
298: String driverPrefix = DatabaseSchema
299: .driverFromSchema(propertyPrefix);
300:
301: // cache the properties - we may need a lot of them
302: ChimeraProperties props = ChimeraProperties.instance();
303:
304: if (dbDriverName == null || dbDriverName.equals("")) {
305: if (driverPrefix != null)
306: dbDriverName = props
307: .getDatabaseDriverName(driverPrefix);
308: if (dbDriverName == null)
309: throw new RuntimeException(
310: "You need to specify the database driver property");
311: }
312: Logging.instance().log("dbschema", 4,
313: "dbdriver class " + dbDriverName);
314:
315: // dynamically load the driver from its default constructor
316: this .m_dbdriver = DatabaseDriver.loadDriver(dbDriverName,
317: driverPrefix, new Object[0]);
318: this .m_dbschemaprops = props
319: .getDatabaseSchemaProperties(propertyPrefix);
320:
321: //instead of the driverPrefix, use the DB prefix
322: //This is because the DB properties are now gotten from example
323: //pegasus.catalog.provenance.db.* instead of
324: //pegasus.catalog.proveance.db.driver.*
325: //Karan Oct 25, 2007. Pegasus Bug Number: 11
326: //http://vtcpc.isi.edu/bugzilla/show_bug.cgi?id=11
327: String dbPrefix = DatabaseSchema.dbFromSchema(propertyPrefix);
328:
329: // Properties dbdriverprops = props.getDatabaseDriverProperties(driverPrefix);
330: // String url = props.getDatabaseURL(driverPrefix);
331:
332: // extract those properties specific to the database driver.
333: // these properties are transparently passed through MINUS the url key.
334: Properties dbdriverprops = props
335: .getDatabaseDriverProperties(dbPrefix);
336: String url = props.getDatabaseURL(dbPrefix);
337:
338: // create a database connection right now, right here
339: // mind, url may be null, which may be legal for some drivers!
340: Logging.instance().log("dbschema", 3,
341: "invoking connect( " + url + " )");
342: this .m_dbdriver.connect(url, dbdriverprops, null);
343:
344: Logging.instance().log("dbschema", 3,
345: "connected to database backend");
346:
347: // prepare statements as necessary in the implementing classes!
348: }
349:
350: /**
351: * Associates a schema with a given database driver.
352: *
353: * @param driver is an instance conforming to the DatabaseDriver API.
354: * @param propertyPrefix is the property prefix string to use.
355: *
356: * @exception SQLException if the driver for the database can be
357: * loaded, but faults when initially accessing the database
358: */
359: public DatabaseSchema(DatabaseDriver driver, String propertyPrefix)
360: throws SQLException, ClassNotFoundException, IOException {
361: Logging.instance().log("dbschema", 3,
362: "accessing DatabaseSchema(DatabaseDriver,String)");
363: this .m_dbdriver = driver;
364:
365: // guess the db driver property prefix from schema prefix
366: String driverPrefix = DatabaseSchema
367: .driverFromSchema(propertyPrefix);
368:
369: // cache the properties - we may need a lot of them
370: ChimeraProperties props = ChimeraProperties.instance();
371:
372: // get database schema properties
373: this .m_dbschemaprops = props
374: .getDatabaseSchemaProperties(propertyPrefix);
375:
376: // extract those properties specific to the database driver.
377: // these properties are transparently passed through MINUS the url key.
378: Properties dbdriverprops = props
379: .getDatabaseDriverProperties(driverPrefix);
380: String url = props.getDatabaseURL(driverPrefix);
381:
382: // create a database connection right now, right here
383: // mind, url may be null, which may be legal for some drivers!
384: Logging.instance().log("dbschema", 3,
385: "invoking connect( " + url + " )");
386: this .m_dbdriver.connect(url, dbdriverprops, null);
387:
388: Logging.instance().log("dbschema", 3,
389: "connected to database backend");
390:
391: // prepare statements as necessary in the implementing classes!
392: }
393:
394: /**
395: * pass-thru to driver.
396: * @return true, if it is feasible to cache results from the driver
397: * false, if requerying the driver is sufficiently fast (e.g. driver
398: * is in main memory, or driver does caching itself).
399: */
400: public boolean cachingMakesSense() {
401: return this .m_dbdriver.cachingMakesSense();
402: }
403:
404: /**
405: * Disassociate from the database driver before finishing.
406: * Mind that performing this action may throw NullPointerException
407: * in later stages!
408: */
409: public void close() throws SQLException {
410: if (this .m_dbdriver != null) {
411: this .m_dbdriver.disconnect();
412: this .m_dbdriver = null;
413: }
414: }
415:
416: /**
417: * Disassociate the database driver cleanly.
418: */
419: protected void finalize() throws Throwable {
420: this .close();
421: super .finalize();
422: }
423:
424: //
425: // papa's little helpers
426: //
427:
428: /**
429: * Adds a string or a SQL-NULL at the current prepared statement
430: * position, depending if the String value is null or not.
431: *
432: * @param ps is the prepared statement to extend
433: * @param pos is the position at which to insert the value
434: * @param s is the String to use, which may be null.
435: */
436: protected void stringOrNull(PreparedStatement ps, int pos, String s)
437: throws SQLException {
438: if (s == null)
439: ps.setNull(pos, Types.VARCHAR);
440: else
441: ps.setString(pos, s);
442: }
443:
444: /**
445: * Adds a BIGINT or a SQL-NULL at the current prepared statement
446: * position, depending if the value is -1 or not. A value of -1
447: * will lead to SQL-NULL.
448: *
449: * @param ps is the prepared statement to extend
450: * @param pos is the position at which to insert the value
451: * @param l is the long to use, which may be null.
452: */
453: protected void longOrNull(PreparedStatement ps, int pos, long l)
454: throws SQLException {
455: if (l == -1)
456: ps.setNull(pos, Types.BIGINT);
457: else {
458: if (m_dbdriver.preferString())
459: ps.setString(pos, Long.toString(l));
460: else
461: ps.setLong(pos, l);
462: }
463: }
464:
465: /**
466: * Converts any given string into a guaranteed non-null value.
467: * Especially the definition triples use empty strings instead of
468: * null values.
469: *
470: * @param s is the string object to look at, which may be null.
471: * @return a string that may be empty, but is not null.
472: */
473: protected String makeNotNull(String s) {
474: return (s == null ? new String() : s);
475: }
476: }
|