/* * This file or a portion of this file is licensed under the terms of * the Globus Toolkit Public License, found in file GTPL, or at * http://www.globus.org/toolkit/download/license.html. This notice must * appear in redistributions of this file, with or without modification. * * Redistributions of this Software, with or without modification, must * reproduce the GTPL in: (1) the Software, or (2) the Documentation or * some other similar material which is provided with the Software (if * any). * * Copyright 1999-2004 University of Chicago and The University of * Southern California. All rights reserved. */ package org.griphyn.vdl.dbschema; import java.sql.*; import java.io.File; import java.io.IOException; import java.util.*; import java.lang.reflect.*; import org.griphyn.vdl.util.ChimeraProperties; import edu.isi.pegasus.common.util.DynamicLoader; import org.griphyn.vdl.classes.Definitions; import org.griphyn.vdl.dbdriver.*; import org.griphyn.vdl.util.Logging; /** * This common schema interface defines the schemas in which the * abstraction layers access any given database. It is independent * of the implementing database, and does so by going via the * database driver class API.<p> * The separation of database driver and schema lowers the implementation * cost, as only N driver and M schemas need to be implemented, instead * of N x M schema-specific database-specific drivers. * * @author Jens-S. Vöckler * @author Yong Zhao * @version $Revision$ * @see org.griphyn.vdl.dbdriver */ public abstract class DatabaseSchema implements Catalog { /** * This is the variable that connect to the lower level database driver. */ protected DatabaseDriver m_dbdriver; /** * This stores properties specific to the schema. Currently unused. */ protected Properties m_dbschemaprops; // // class methods // /** * Instantiates the appropriate leaf schema according to property values. * This method is a factory. * * @param dbSchemaName is the name of the class that conforms to * the DatabaseSchema API. This class will be dynamically loaded. * If the passed value is <code>null</code>, which should be the * default, the value of property vds.db.schema is taken. * @param propertyPrefix is the property prefix string to use. * @param arguments are arguments to the constructor of the driver * to load. Please use "new Object[0]" for the default constructor. * * @exception ClassNotFoundException if the schema for the database * cannot be loaded. You might want to check your CLASSPATH, too. * @exception NoSuchMethodException if the schema's constructor interface * does not comply with the database driver API. * @exception InstantiationException if the schema class is an abstract * class instead of a concrete implementation. * @exception IllegalAccessException if the constructor for the schema * class it not publicly accessible to this package. * @exception InvocationTargetException if the constructor of the schema * throws an exception while being dynamically loaded. * * @see org.griphyn.vdl.util.ChimeraProperties */ static public DatabaseSchema loadSchema( String dbSchemaName, String propertyPrefix, Object[] arguments ) throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Logging log = Logging.instance(); log.log( "dbschema", 3, "accessing loadSchema( " + ( dbSchemaName == null ? "(null)" : dbSchemaName ) + ", " + ( propertyPrefix == null ? "(null)" : propertyPrefix ) + " )" ); // determine the database schema to load if ( dbSchemaName == null ) { // get it by property prefix dbSchemaName = ChimeraProperties.instance() .getDatabaseSchemaName( propertyPrefix ); if ( dbSchemaName == null ) throw new RuntimeException( "You need to specify the " + propertyPrefix + " property" ); } // syntactic sugar adds absolute class prefix if ( dbSchemaName.indexOf('.') == -1 ) { // how about xxx.getClass().getPackage().getName()? dbSchemaName = "org.griphyn.vdl.dbschema." + dbSchemaName; } // POSTCONDITION: we have now a fully-qualified class name log.log( "dbschema", 3, "trying to load " + dbSchemaName ); DynamicLoader dl = new DynamicLoader(dbSchemaName); DatabaseSchema result = (DatabaseSchema) dl.instantiate(arguments); // done if ( result == null ) log.log( "dbschema", 0, "unable to load " + dbSchemaName ); else log.log( "dbschema", 3, "successfully loaded " + dbSchemaName ); return result; } /** * Convenience method instantiates the appropriate child according to * property values. Effectively, the following is being called: * * <pre> * loadSchema( null, propertyPrefix, new Object[0] ); * </pre> * * @param propertyPrefix is the property prefix string to use. * * @exception ClassNotFoundException if the schema for the database * cannot be loaded. You might want to check your CLASSPATH, too. * @exception NoSuchMethodException if the schema's constructor interface * does not comply with the database driver API. * @exception InstantiationException if the schema class is an abstract * class instead of a concrete implementation. * @exception IllegalAccessException if the constructor for the schema * class it not publicly accessible to this package. * @exception InvocationTargetException if the constructor of the schema * throws an exception while being dynamically loaded. * * @see #loadSchema( String, String, Object[] ) * @see org.griphyn.vdl.util.ChimeraProperties */ static public DatabaseSchema loadSchema( String propertyPrefix ) throws ClassNotFoundException,IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { return loadSchema( null, propertyPrefix, new Object[0] ); } // // instance methods // /** * Minimalistic default ctor. This constructor does nothing, * and loads nothing. But it initializes the empty schema props. */ protected DatabaseSchema() { Logging.instance().log( "dbschema", 3, "accessing DatabaseSchema()" ); this.m_dbdriver = null; this.m_dbschemaprops = new Properties(); } /** * Connects to the database, this method does not rely on global * property values, instead, each property has to be provided * explicitly. * * @param dbDriverName is the name of the class that conforms to * the DatabaseDriver API. This class will be dynamically loaded. * @param url is the database url * @param dbDriverProperties holds properties specific to the * database driver. * @param dbSchemaProperties holds properties specific to the * database schema. * * @exception ClassNotFoundException if the driver for the database * cannot be loaded. You might want to check your CLASSPATH, too. * @exception NoSuchMethodException if the driver's constructor interface * does not comply with the database driver API. * @exception InstantiationException if the driver class is an abstract * class instead of a concrete implementation. * @exception IllegalAccessException if the constructor for the driver * class it not publicly accessible to this package. * @exception InvocationTargetException if the constructor of the driver * throws an exception while being dynamically loaded. * @exception SQLException if the driver for the database can be * loaded, but faults when initially accessing the database */ public DatabaseSchema( String dbDriverName, String url, Properties dbDriverProperties, Properties dbSchemaProperties) throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, SQLException { Logging.instance().log( "dbschema", 3, "accessing DatabaseSchema(String,String, Properties, Properties)" ); // dynamically load the driver from its default constructor this.m_dbdriver = DatabaseDriver.loadDriver( dbDriverName, null, new Object[0] ); this.m_dbschemaprops = dbSchemaProperties; // create a database connection right now, right here // mind, url may be null, which may be legal for some drivers! Logging.instance().log( "dbschema", 3, "invoking connect( " + url + " )" ); this.m_dbdriver.connect( url, dbDriverProperties, null ); Logging.instance().log( "dbschema", 3, "connected to database backend" ); // prepare statements as necessary in the implementing classes! } /** * Guesses from the schema prefix the driver prefix. * * @param schemaPrefix is the property key prefix for the schema. * @return the guess for the driver's prefix, may be <code>null</code> */ private static String driverFromSchema( String schemaPrefix ) { String result = null; if ( schemaPrefix != null && schemaPrefix.endsWith(".schema") ) result = schemaPrefix.substring( 0, schemaPrefix.length()-7 ) + ".driver"; Logging.instance().log( "dbschema", 4, "dbdriver prefix guess " + ( result == null ? "(null)" : result ) ); return result; } /** * Guesses from the schema prefix the db prefix. * * @param schemaPrefix is the property key prefix for the schema. * * @return the guess for the db properties prefix, may be <code>null</code> */ private static String dbFromSchema( String schemaPrefix ) { String result = null; if ( schemaPrefix != null && schemaPrefix.endsWith(".schema") ) result = schemaPrefix.substring( 0, schemaPrefix.length()-7 ); Logging.instance().log( "dbschema", 4, "db propertiesr prefix guess " + ( result == null ? "(null)" : result ) ); return result; } /** * Connects to the database as specified by the properties, and * checks the schema implementation. Makes heavy use of global * property values. * * @param dbDriverName is the name of the class that conforms to * the DatabaseDriver API. This class will be dynamically loaded. * If the passed value is <code>null</code>, which should be the * default, the value of property vds.db.*.driver is taken. * @param propertyPrefix is the property prefix string to use. * * @exception ClassNotFoundException if the driver for the database * cannot be loaded. You might want to check your CLASSPATH, too. * @exception NoSuchMethodException if the driver's constructor interface * does not comply with the database driver API. * @exception InstantiationException if the driver class is an abstract * class instead of a concrete implementation. * @exception IllegalAccessException if the constructor for the driver * class it not publicly accessible to this package. * @exception InvocationTargetException if the constructor of the driver * throws an exception while being dynamically loaded. * @exception SQLException if the driver for the database can be * loaded, but faults when initially accessing the database */ public DatabaseSchema( String dbDriverName, String propertyPrefix ) throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, SQLException { Logging.instance().log( "dbschema", 3, "accessing DatabaseSchema(String,String)" ); // guess the db driver property prefix from schema prefix String driverPrefix = DatabaseSchema.driverFromSchema(propertyPrefix); // cache the properties - we may need a lot of them ChimeraProperties props = ChimeraProperties.instance(); if ( dbDriverName == null || dbDriverName.equals("") ) { if ( driverPrefix != null ) dbDriverName = props.getDatabaseDriverName(driverPrefix); if ( dbDriverName == null ) throw new RuntimeException( "You need to specify the database driver property" ); } Logging.instance().log( "dbschema", 4, "dbdriver class " + dbDriverName ); // dynamically load the driver from its default constructor this.m_dbdriver = DatabaseDriver.loadDriver( dbDriverName, driverPrefix, new Object[0] ); this.m_dbschemaprops = props.getDatabaseSchemaProperties( propertyPrefix ); //instead of the driverPrefix, use the DB prefix //This is because the DB properties are now gotten from example //pegasus.catalog.provenance.db.* instead of //pegasus.catalog.proveance.db.driver.* //Karan Oct 25, 2007. Pegasus Bug Number: 11 //http://vtcpc.isi.edu/bugzilla/show_bug.cgi?id=11 String dbPrefix = DatabaseSchema.dbFromSchema( propertyPrefix ); // Properties dbdriverprops = props.getDatabaseDriverProperties(driverPrefix); // String url = props.getDatabaseURL(driverPrefix); // extract those properties specific to the database driver. // these properties are transparently passed through MINUS the url key. Properties dbdriverprops = props.getDatabaseDriverProperties( dbPrefix ); String url = props.getDatabaseURL( dbPrefix ); // create a database connection right now, right here // mind, url may be null, which may be legal for some drivers! Logging.instance().log( "dbschema", 3, "invoking connect( " + url + " )" ); this.m_dbdriver.connect( url, dbdriverprops, null ); Logging.instance().log( "dbschema", 3, "connected to database backend" ); // prepare statements as necessary in the implementing classes! } /** * Associates a schema with a given database driver. * * @param driver is an instance conforming to the DatabaseDriver API. * @param propertyPrefix is the property prefix string to use. * * @exception SQLException if the driver for the database can be * loaded, but faults when initially accessing the database */ public DatabaseSchema( DatabaseDriver driver, String propertyPrefix ) throws SQLException, ClassNotFoundException, IOException { Logging.instance().log( "dbschema", 3, "accessing DatabaseSchema(DatabaseDriver,String)" ); this.m_dbdriver = driver; // guess the db driver property prefix from schema prefix String driverPrefix = DatabaseSchema.driverFromSchema(propertyPrefix); // cache the properties - we may need a lot of them ChimeraProperties props = ChimeraProperties.instance(); // get database schema properties this.m_dbschemaprops = props.getDatabaseSchemaProperties( propertyPrefix ); // extract those properties specific to the database driver. // these properties are transparently passed through MINUS the url key. Properties dbdriverprops = props.getDatabaseDriverProperties(driverPrefix); String url = props.getDatabaseURL(driverPrefix); // create a database connection right now, right here // mind, url may be null, which may be legal for some drivers! Logging.instance().log( "dbschema", 3, "invoking connect( " + url + " )" ); this.m_dbdriver.connect( url, dbdriverprops, null ); Logging.instance().log( "dbschema", 3, "connected to database backend" ); // prepare statements as necessary in the implementing classes! } /** * pass-thru to driver. * @return true, if it is feasible to cache results from the driver * false, if requerying the driver is sufficiently fast (e.g. driver * is in main memory, or driver does caching itself). */ public boolean cachingMakesSense() { return this.m_dbdriver.cachingMakesSense(); } /** * Disassociate from the database driver before finishing. * Mind that performing this action may throw NullPointerException * in later stages! */ public void close() throws SQLException { if ( this.m_dbdriver != null ) { this.m_dbdriver.disconnect(); this.m_dbdriver = null; } } /** * Disassociate the database driver cleanly. */ protected void finalize() throws Throwable { this.close(); super.finalize(); } // // papa's little helpers // /** * Adds a string or a SQL-NULL at the current prepared statement * position, depending if the String value is null or not. * * @param ps is the prepared statement to extend * @param pos is the position at which to insert the value * @param s is the String to use, which may be null. */ protected void stringOrNull( PreparedStatement ps, int pos, String s ) throws SQLException { if ( s == null ) ps.setNull( pos, Types.VARCHAR ); else ps.setString( pos, s ); } /** * Adds a BIGINT or a SQL-NULL at the current prepared statement * position, depending if the value is -1 or not. A value of -1 * will lead to SQL-NULL. * * @param ps is the prepared statement to extend * @param pos is the position at which to insert the value * @param l is the long to use, which may be null. */ protected void longOrNull( PreparedStatement ps, int pos, long l ) throws SQLException { if ( l == -1 ) ps.setNull( pos, Types.BIGINT ); else { if ( m_dbdriver.preferString() ) ps.setString( pos, Long.toString(l) ); else ps.setLong( pos, l ); } } /** * Converts any given string into a guaranteed non-null value. * Especially the definition triples use empty strings instead of * null values. * * @param s is the string object to look at, which may be null. * @return a string that may be empty, but is not null. */ protected String makeNotNull( String s ) { return ( s == null ? new String() : s ); } }