/** * This file or a portion of this file is licensed under the terms of * the Globus Toolkit Public License, found at $PEGASUS_HOME/GTPL or * 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 edu.isi.pegasus.planner.ranking; import edu.isi.pegasus.common.logging.LogManagerFactory; import edu.isi.pegasus.planner.common.PegasusProperties; import edu.isi.pegasus.common.logging.LogManager; import edu.isi.pegasus.common.util.CommonProperties; import edu.isi.pegasus.planner.catalog.Catalog; import java.util.Properties; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedList; import java.io.PrintWriter; import java.io.FileWriter; import java.io.IOException; import java.io.File; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.DriverManager; import java.sql.ResultSet; /** * This class is responsible for the fetching the DAX'es on the basis of the * request ID's from the Windward Provenance Tracking Catalog. If there are * more than one way's to get the DAX's then it should be an interface. * * @author Karan Vahi * @version $Revision$ */ public class GetDAX { /** * Prefix for the property subset to use with this catalog. */ public static final String c_prefix = "pegasus.catalog.provenance.windward"; /** * The DB Driver properties prefix. */ public static final String DB_PREFIX = "pegasus.catalog.provenance.windward.db"; /** * The statement to prepare to slurp attributes. */ private static final String mCStatements[] = { // 0: "SELECT dax FROM instances_and_daxes WHERE seed_id=?", }; /** * Maintains the connection to the database over the lifetime of * this instance. */ private Connection mConnection = null; /** * Maintains an essential set of prepared statement, ready to use. */ private PreparedStatement mStatements[] = null; /** * The properties passed to the client. */ private Properties mProps; /** * The instance to the Logging manager. */ private LogManager mLogger; /** * The default constructor. */ public GetDAX() { mLogger = LogManagerFactory.loadSingletonInstance(); // make connection defunct mConnection = null; mStatements = null; } /** * A convenience method to connect on the basis of PegasusProperties. * Eventually this logic should go in the invoking code or factory. * * @param properties PegasusProperties * * @return boolean */ public boolean connect( PegasusProperties properties ){ CommonProperties props = properties.getVDSProperties(); Properties connect = props.matchingSubset( GetDAX.c_prefix, false ); //get the default db driver properties in first pegasus.catalog.*.db.driver.* Properties db = props.matchingSubset( Catalog.DB_ALL_PREFIX, false ); //now overload with the work catalog specific db properties. //pegasus.catalog.work.db.driver.* db.putAll( props.matchingSubset( GetDAX.DB_PREFIX , false ) ); //to make sure that no confusion happens. //add the db prefix to all the db properties for( Enumeration e = db.propertyNames(); e.hasMoreElements(); ){ String key = (String)e.nextElement(); connect.put( "db." + key, db.getProperty( key )); } return connect( connect ); } /** * Establishes a connection to the database from the properties. You * can specify a <tt>driver</tt> property to contain the class name of * the JDBC driver for your database. This property will be removed * before attempting to connect. You must speficy a <tt>url</tt> * property to describe the connection. It will be removed before * attempting to connect. * * @param props is the property table with sufficient settings to * establish a link with the database. The minimum key required key is * "url", and possibly "driver". Any other keys depend on the database * driver. * * @return true if connected, false if failed to connect. * * @see java.sql.DriverManager#getConnection( String, Properties ) * */ public boolean connect( Properties props ) { boolean result = false; // class loader: Will propagate any runtime errors!!! String driver = (String) props.remove("db.driver"); Properties localProps = CommonProperties.matchingSubset( (Properties)props.clone(), "db", false ); String url = (String) localProps.remove("url"); if( url == null ){ //try to construct the jdbc string from the properties url = getJDBCURL( driver, localProps ); } if (url == null || url.length() == 0) { return result; } try { if (driver != null) { //only support mysql and postgres for time being if( driver.equalsIgnoreCase( "MySQL") ){ driver = "com.mysql.jdbc.Driver"; } else if ( driver.equalsIgnoreCase( "Postgres" )){ driver = "org.postgresql.Driver"; } mLogger.log( "Driver being used to connect to Work Catalog is " + driver, LogManager.DEBUG_MESSAGE_LEVEL ); Class.forName(driver); } } catch (Exception e) { mLogger.log( "While connecting to Windward Provenance Catalog", e, LogManager.DEBUG_MESSAGE_LEVEL ); return result; } try { mLogger.log( "Connecting with jdbc url " + url , LogManager.DEBUG_MESSAGE_LEVEL ); mConnection = DriverManager.getConnection( url, localProps ); // m_autoinc = mConnection.getMetaData().supportsGetGeneratedKeys(); // prepared statements are Singletons -- prepared on demand mStatements = new PreparedStatement[ mCStatements.length ]; for (int i = 0; i < mCStatements.length; ++i) { mStatements[i] = null; } result = true; } catch (SQLException e) { mLogger.log( "While Windward Provenance Catalog", e , LogManager.DEBUG_MESSAGE_LEVEL ); result = false; } return result; } /** * Constructs the jdbc url on the basis fo the driver and db properties. * * @param driver the driver being used. * @param properties the db properites * * @return the jdbc url, else null if unable to construct */ protected String getJDBCURL( String driver, Properties properties ){ if( driver == null ) { return null; } StringBuffer result = new StringBuffer(); result.append( "jdbc:" ).append( driver.toLowerCase() ).append( "://" ); String hostname = (String)properties.remove( "hostname" ); if( hostname == null || hostname.length() == 0 ){ return null; } result.append( hostname ); String database = (String)properties.remove( "database" ); if( database == null || database.length() == 0 ){ return null; } result.append( "/" ).append( database ); return result.toString(); } /** * Given a request ID it fetches the DAX's from the DB and writes out to * the directory passed. * * @param id the request id. * @param dir the directory where the DAX'es need to be placed. * * @return a Collection of basenames fo the DAX'es placed in the directory. */ public Collection<String> get( String id, String dir ){ if( isClosed() ){ throw new RuntimeException( "The connection to backend database is closed" ); } //if if( dir == null ){ throw new RuntimeException( "Unable to write out to null directory" ); } Collection result = new LinkedList(); //get the prepared statement int which = 0; try{ //do sanity check on dir sanityCheck( new File( dir ) ); PreparedStatement ps = getStatement(which); ps.setString( 1, id ); mLogger.log( "Executing query " + ps.toString(), LogManager.DEBUG_MESSAGE_LEVEL ); ResultSet rs = ps.executeQuery(); int index = 0; while ( rs.next() ) { index++; String xml = rs.getString( 1 ); //construct the name of the file on index and //request id only. StringBuffer name = new StringBuffer(); name.append( id ).append( "_" ).append( index ); name.append( ".dax" ); //pipe the dax to the directory. File dax = new File( dir, name.toString() ); PrintWriter pw = new PrintWriter( new FileWriter( dax ) ); pw.println( xml ); pw.close(); //add to the result result.add( dax.getAbsolutePath() ); } rs.close(); } catch (SQLException e) { throw new RuntimeException( "Unable to query from Windward Provenance Catalog " , e); } catch ( IOException ioe ) { throw new RuntimeException( "IOException while trying to write to dir " + dir , ioe ); } return result; } /** * Predicate to check, if the connection with the catalog's * implementation is still active. This helps determining, if it makes * sense to call <code>close()</code>. * * @return true, if the implementation is disassociated, false otherwise. * @see #close() */ public boolean isClosed() { return ( mConnection == null ); } /** * Explicitely free resources before the garbage collection hits. */ public void close() { if (mConnection != null) { try { if (!mConnection.getAutoCommit()) { mConnection.commit(); } } catch (SQLException e) { // ignore } } if (mStatements != null) { try { for (int i = 0; i < mCStatements.length; ++i) { if (mStatements[i] != null) { mStatements[i].close(); mStatements[i] = null; } } } catch (SQLException e) { // ignore } finally { mStatements = null; } } if (mConnection != null) { try { mConnection.close(); } catch (SQLException e) { // ignore } finally { mConnection = null; } } } /** * Singleton manager for prepared statements. This instruction * checks that a prepared statement is ready to use, and will * create an instance of the prepared statement, if it was unused * previously. * * @param i is the index which prepared statement to check. * @return a handle to the prepared statement. * * * @throws SQLException in case of unable to delete entry. */ protected PreparedStatement getStatement(int i) throws SQLException { if (mStatements[i] == null) { mStatements[i] = mConnection.prepareStatement(mCStatements[i]); } else { mStatements[i].clearParameters(); } return mStatements[i]; } /** * Checks the destination location for existence, if it can * be created, if it is writable etc. * * @param dir is the new base directory to optionally create. * * @throws IOException in case of error while writing out files. */ protected static void sanityCheck( File dir ) throws IOException{ if ( dir.exists() ) { // location exists if ( dir.isDirectory() ) { // ok, isa directory if ( dir.canWrite() ) { // can write, all is well return; } else { // all is there, but I cannot write to dir throw new IOException( "Cannot write to existing directory " + dir.getPath() ); } } else { // exists but not a directory throw new IOException( "Destination " + dir.getPath() + " already " + "exists, but is not a directory." ); } } else { // does not exist, try to make it if ( ! dir.mkdirs() ) { throw new IOException( "Unable to create directory " + dir.getPath() ); } } } /** * For Testing purposes only. * * @param args the arguments passed. */ public static void main( String[] args ){ GetDAX d = new GetDAX(); LogManagerFactory.loadSingletonInstance().setLevel( LogManager.DEBUG_MESSAGE_LEVEL ); System.out.println( "Connecting to database " + d.connect( PegasusProperties.getInstance( ) ) ); //d.get( "RPaper-ModelerThenClassifier-d3206cf5-b3ad-4c9d-9f08-5d25653d5ccf", null ); Collection daxes = d.get( "RPaper-ModelerThenClassifier-a93169ee-ed72-4d4b-be99-f6d69ae29e04" , "/tmp/wings" ); System.out.println( "DAX'es written out are " + daxes ); d.close(); } }