/*
* 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 );
}
}