0001: /*
0002: * This file or a portion of this file is licensed under the terms of
0003: * the Globus Toolkit Public License, found in file GTPL, or at
0004: * http://www.globus.org/toolkit/download/license.html. This notice must
0005: * appear in redistributions of this file, with or without modification.
0006: *
0007: * Redistributions of this Software, with or without modification, must
0008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
0009: * some other similar material which is provided with the Software (if
0010: * any).
0011: *
0012: * Copyright 1999-2004 University of Chicago and The University of
0013: * Southern California. All rights reserved.
0014: */
0015:
0016: package org.griphyn.common.catalog.replica;
0017:
0018: import java.util.*;
0019: import java.sql.*;
0020: import org.griphyn.common.catalog.Catalog;
0021: import org.griphyn.common.catalog.ReplicaCatalog;
0022: import org.griphyn.common.catalog.ReplicaCatalogEntry;
0023: import org.griphyn.common.util.VDSProperties;
0024: import org.griphyn.cPlanner.common.LogManager;
0025:
0026: /**
0027: * This class implements a replica catalog on top of a simple table in a
0028: * JDBC database. This enables a variety of replica catalog
0029: * implementations in a transactionally safe, concurrent environment.
0030: * The table must be defined using the statements appropriate for your
0031: * database - they are part of the setup in $PEGASUS_HOME/sql/.
0032: *
0033: * If you chose to use an unsupported database, please check, if your
0034: * database either supports sequence number, or if it supports auto
0035: * increment columns. If your database supports sequences (e.g.
0036: * PostGreSQL), you can use a setup similar to the following (for
0037: * Oracle, the autoinc can be implemented via a trigger).
0038: *
0039: * <pre>
0040: * create sequence rc_lfn_id;
0041: *
0042: * create table rc_lfn (
0043: * id bigint default nextval('rc_lfn_id'::text),
0044: * lfn varchar(255) not null,
0045: * pfn varchar(255) not null,
0046: *
0047: * constraint pk_rc_lfn primary key(id),
0048: * constraint sk_rc_lfn unique(lfn,pfn)
0049: * );
0050: *
0051: * create index idx_rc_lfn on rc_lfn(lfn);
0052: *
0053: * create table rc_attr (
0054: * id bigint,
0055: * name varchar(64) not null,
0056: * value varchar(255) not null,
0057: *
0058: * constraint pk_rc_attr primary key(id,name),
0059: * constraint fk_rc_attr foreign key(id) references rc_lfn(id) on delete cascade
0060: * );
0061: *
0062: * create index idx_rc_attr on rc_attr(name);
0063: * </pre>
0064: *
0065: * In case of databases that do not support sequences (e.g. MySQL), do
0066: * not specify the <code>create sequence</code>, and use an
0067: * auto-increment column for the primary key instead, e.g.:
0068: *
0069: * <pre>
0070: * create table rc_lfn (
0071: * id bigint default null auto_increment,
0072: * lfn varchar(255) not null,
0073: * pfn varchar(255) not null,
0074: *
0075: * constraint pk_rc_lfn primary key(id),
0076: * constraint sk_rc_lfn unique(lfn,pfn)
0077: * );
0078: *
0079: * create index idx_rc_lfn on rc_lfn(lfn);
0080: *
0081: * create table rc_attr (
0082: * id bigint,
0083: * name varchar(64) not null,
0084: * value varchar(255) not null,
0085: *
0086: * constraint pk_rc_attr primary key(id,name),
0087: * constraint fk_rc_attr foreign key id references rc_lfn(id) on delete cascade
0088: * );
0089: *
0090: * create index idx_rc_attr on rc_attr(name);
0091: * </pre>
0092: *
0093: * The site attribute should be specified whenever possible. For the
0094: * shell planner, it will always be of value "local".
0095: *
0096: * @author Jens-S. Vöckler
0097: * @author Yong Zhao
0098: * @version $Revision: 83 $
0099: */
0100: public class JDBCRC implements ReplicaCatalog {
0101: /**
0102: * This message is sent whenever one of the member function is executed
0103: * which relies on an established database context.
0104: */
0105: private static final String c_error = "The database connection is not established";
0106:
0107: /**
0108: * Maintains the connection to the database over the lifetime of
0109: * this instance.
0110: */
0111: protected Connection mConnection = null;
0112:
0113: /**
0114: * Maintains an essential set of prepared statement, ready to use.
0115: */
0116: protected PreparedStatement mStatements[] = null;
0117:
0118: /**
0119: * The handle to the logging object.
0120: */
0121: protected LogManager mLogger;
0122:
0123: /**
0124: * The statement to prepare to slurp attributes.
0125: */
0126: private static final String mCStatements[] = { // 0:
0127: "SELECT name,value FROM rc_attr WHERE id=?",
0128: // 1:
0129: "SELECT id,pfn FROM rc_lfn WHERE lfn=?",
0130: // 2:
0131: "SELECT r.id,r.pfn FROM rc_lfn r, rc_attr a WHERE r.id=a.id"
0132: + " AND r.lfn=? AND a.name=? AND a.value=?",
0133: // 3:
0134: "SELECT r.id,r.pfn FROM rc_lfn r, rc_attr a WHERE r.id=a.id"
0135: + " AND r.lfn=? AND a.name=? AND a.value IS NULL",
0136: // 4:
0137: "INSERT INTO rc_attr(id,name,value) VALUES(?,?,?)",
0138: // 5:
0139: "DELETE FROM rc_lfn WHERE lfn=?",
0140: // 6:
0141: "DELETE FROM rc_lfn WHERE id IN"
0142: + " ( SELECT id FROM rc_attr WHERE name=? AND value=? )",
0143: // 7:
0144: "DELETE FROM rc_lfn WHERE id IN"
0145: + " ( SELECT id FROM rc_attr WHERE name=? AND value IS NULL )",
0146: // 8:
0147: "DELETE FROM rc_lfn WHERE lfn=? AND id IN"
0148: + " ( SELECT id FROM rc_attr WHERE name=? AND value=? )",
0149: // 9:
0150: "DELETE FROM rc_lfn WHERE lfn=? AND id IN"
0151: + " ( SELECT id FROM rc_attr WHERE name=? AND value IS NULL )", };
0152:
0153: /**
0154: * Remembers if obtaining generated keys will work or not.
0155: */
0156: private boolean m_autoinc = false;
0157:
0158: /**
0159: * Convenience c'tor: Establishes the connection to the replica
0160: * catalog database. The usual suspects for the class name include:
0161: *
0162: * <pre>
0163: * org.postgresql.Driver
0164: * com.mysql.jdbc.Driver
0165: * com.microsoft.jdbc.sqlserver.SQLServerDriver
0166: * SQLite.JDBCDriver
0167: * sun.jdbc.odbc.JdbcOdbcDriver
0168: * </pre>
0169: *
0170: * @param jdbc is a string containing the full name of the java class
0171: * that must be dynamically loaded. This is usually an external jar
0172: * file which contains the Java database driver.
0173: * @param url is the database driving URL. This string is database
0174: * specific, and tell the JDBC driver, at which host and port the
0175: * database listens, permits additional arguments, and selects the
0176: * database inside the rDBMS to connect to. Please refer to your
0177: * JDBC driver documentation for the format and permitted values.
0178: * @param username is the database user account name to connect with.
0179: * @param password is the database account password to use.
0180: *
0181: * @throws LinkageError if linking the dynamically loaded driver fails.
0182: * This is a run-time error, and does not need to be caught.
0183: * @throws ExceptionInInitializerError if the initialization function
0184: * of the driver's instantiation threw an exception itself. This is a
0185: * run-time error, and does not need to be caught.
0186: * @throws ClassNotFoundException if the class in your jdbc parameter
0187: * cannot be found in your given CLASSPATH environment. Callers must
0188: * catch this exception.
0189: * @throws SQLException if something goes awry with the database.
0190: * Callers must catch this exception.
0191: */
0192: public JDBCRC(String jdbc, String url, String username,
0193: String password) throws LinkageError,
0194: ExceptionInInitializerError, ClassNotFoundException,
0195: SQLException {
0196: this ();
0197: // load database driver jar
0198: Class.forName(jdbc);
0199: // may throw LinkageError,
0200: // may throw ExceptionInInitializerError,
0201: // may throw ClassNotFoundException
0202:
0203: // establish connection to database generically
0204: connect(url, username, password);
0205: // may throws SQLException
0206: }
0207:
0208: /**
0209: * Default empty constructor creates an object that is not yet connected
0210: * to any database. You must use support methods to connect before this
0211: * instance becomes usable.
0212: *
0213: * @see #connect( String, String, String )
0214: */
0215: public JDBCRC() {
0216: // make connection defunc
0217: mConnection = null;
0218: mStatements = null;
0219: mLogger = LogManager.getInstance();
0220: }
0221:
0222: /**
0223: * Connects to the database. This is effectively an accessor to
0224: * initialize the internal connection instance variable. <b>Warning!
0225: * You must call {@link java.lang.Class#forName( String )} yourself
0226: * to load the database JDBC driver jar!</b>
0227: *
0228: * @param url is the database driving URL. This string is database
0229: * specific, and tell the JDBC driver, at which host and port the
0230: * database listens, permits additional arguments, and selects the
0231: * database inside the rDBMS to connect to. Please refer to your
0232: * JDBC driver documentation for the format and permitted values.
0233: * @param username is the database user account name to connect with.
0234: * @param password is the database account password to use.
0235: * @throws SQLException if something goes awry with the database.
0236: * Callers must catch this exception.
0237: * @see #JDBCRC( String, String, String, String )
0238: * @see java.sql.DriverManager#getConnection( String, String, String )
0239: */
0240: public void connect(String url, String username, String password)
0241: throws SQLException {
0242: // establish connection to database generically
0243: mConnection = DriverManager.getConnection(url, username,
0244: password);
0245:
0246: // may throws SQLException
0247: m_autoinc = mConnection.getMetaData()
0248: .supportsGetGeneratedKeys();
0249:
0250: // prepared statements are Singletons -- prepared on demand
0251: mStatements = new PreparedStatement[mCStatements.length];
0252: for (int i = 0; i < mCStatements.length; ++i)
0253: mStatements[i] = null;
0254: }
0255:
0256: /**
0257: * Establishes a connection to the database from the properties. You
0258: * can specify a <tt>driver</tt> property to contain the class name of
0259: * the JDBC driver for your database. This property will be removed
0260: * before attempting to connect. You must speficy a <tt>url</tt>
0261: * property to describe the connection. It will be removed before
0262: * attempting to connect.
0263: *
0264: * @param props is the property table with sufficient settings to
0265: * establish a link with the database. The minimum key required key is
0266: * "url", and possibly "driver". Any other keys depend on the database
0267: * driver.
0268: * @return true if connected, false if failed to connect.
0269: * @see java.sql.DriverManager#getConnection( String, Properties )
0270: *
0271: * @throws Error subclasses for runtime errors in the class loader.
0272: */
0273: public boolean connect(Properties props) {
0274:
0275: boolean result = false;
0276: // class loader: Will propagate any runtime errors!!!
0277: String driver = (String) props.remove("db.driver");
0278:
0279: Properties localProps = VDSProperties.matchingSubset(
0280: (Properties) props.clone(), "db", false);
0281:
0282: String url = (String) localProps.remove("url");
0283: if (url == null || url.length() == 0) {
0284: return result;
0285: }
0286:
0287: try {
0288: if (driver != null) {
0289: //only support mysql and postgres for time being
0290: if (driver.equalsIgnoreCase("MySQL")) {
0291: driver = "com.mysql.jdbc.Driver";
0292: } else if (driver.equalsIgnoreCase("Postgres")) {
0293: driver = "org.postgresql.Driver";
0294: }
0295: Class.forName(driver);
0296: }
0297: } catch (Exception e) {
0298: mLogger.log("While connecting to JDBCRC Replica Catalog",
0299: e, LogManager.DEBUG_MESSAGE_LEVEL);
0300: return result;
0301: }
0302:
0303: try {
0304: mConnection = DriverManager.getConnection(url, localProps);
0305: m_autoinc = mConnection.getMetaData()
0306: .supportsGetGeneratedKeys();
0307:
0308: // prepared statements are Singletons -- prepared on demand
0309: mStatements = new PreparedStatement[mCStatements.length];
0310: for (int i = 0; i < mCStatements.length; ++i) {
0311: mStatements[i] = null;
0312: }
0313:
0314: result = true;
0315: } catch (SQLException e) {
0316: mLogger.log("While connecting to JDBCRC Replica Catalog",
0317: e, LogManager.DEBUG_MESSAGE_LEVEL);
0318: result = false;
0319: }
0320:
0321: return result;
0322: }
0323:
0324: /**
0325: * Explicitely free resources before the garbage collection hits.
0326: */
0327: public void close() {
0328:
0329: if (mConnection != null) {
0330: try {
0331: if (!mConnection.getAutoCommit())
0332: mConnection.commit();
0333: } catch (SQLException e) {
0334: // ignore
0335: }
0336: }
0337:
0338: if (mStatements != null) {
0339: try {
0340: for (int i = 0; i < mCStatements.length; ++i) {
0341: if (mStatements[i] != null) {
0342: mStatements[i].close();
0343: mStatements[i] = null;
0344: }
0345: }
0346: } catch (SQLException e) {
0347: // ignore
0348: } finally {
0349: mStatements = null;
0350: }
0351: }
0352:
0353: if (mConnection != null) {
0354: try {
0355: mConnection.close();
0356: } catch (SQLException e) {
0357: // ignore
0358: } finally {
0359: mConnection = null;
0360: }
0361: }
0362: }
0363:
0364: /**
0365: * Predicate to check, if the connection with the catalog's
0366: * implementation is still active. This helps determining, if it makes
0367: * sense to call <code>close()</code>.
0368: *
0369: * @return true, if the implementation is disassociated, false otherwise.
0370: * @see #close()
0371: */
0372: public boolean isClosed() {
0373: return (mConnection == null);
0374: }
0375:
0376: /**
0377: * Quotes a string that may contain special SQL characters.
0378: * @param s is the raw string.
0379: * @return the quoted string, which may be just the input string.
0380: */
0381: protected String quote(String s) {
0382: if (s.indexOf('\'') != -1) {
0383: StringBuffer result = new StringBuffer();
0384: for (int i = 0; i < s.length(); ++i) {
0385: char ch = s.charAt(i);
0386: result.append(ch);
0387: if (ch == '\'')
0388: result.append(ch);
0389: }
0390: return result.toString();
0391: } else {
0392: return s;
0393: }
0394: }
0395:
0396: /**
0397: * Singleton manager for prepared statements. This instruction
0398: * checks that a prepared statement is ready to use, and will
0399: * create an instance of the prepared statement, if it was unused
0400: * previously.
0401: *
0402: * @param i is the index which prepared statement to check.
0403: * @return a handle to the prepared statement.
0404: */
0405: protected PreparedStatement getStatement(int i) throws SQLException {
0406: if (mStatements[i] == null)
0407: mStatements[i] = mConnection
0408: .prepareStatement(mCStatements[i]);
0409: else
0410: mStatements[i].clearParameters();
0411:
0412: return mStatements[i];
0413: }
0414:
0415: /**
0416: * Retrieves the entry for a given filename and site handle from the
0417: * replica catalog.
0418: *
0419: * @param lfn is the logical filename to obtain information for.
0420: * @param handle is the resource handle to obtain entries for.
0421: * @return the (first) matching physical filename, or
0422: * <code>null</code> if no match was found.
0423: */
0424: public String lookup(String lfn, String handle) {
0425: String result = null;
0426: int which = (handle == null ? 3 : 2);
0427: String query = mCStatements[which];
0428:
0429: // sanity check
0430: if (lfn == null)
0431: return result;
0432: if (mConnection == null)
0433: throw new RuntimeException(c_error);
0434:
0435: try {
0436: PreparedStatement ps = getStatement(which);
0437: ps.setString(1, quote(lfn));
0438: ps.setString(2, quote(ReplicaCatalogEntry.RESOURCE_HANDLE));
0439: if (handle != null)
0440: ps.setString(3, quote(handle));
0441:
0442: // there should only be one result
0443: ResultSet rs = ps.executeQuery();
0444: if (rs.next())
0445: result = rs.getString("pfn");
0446:
0447: rs.close();
0448: } catch (SQLException e) {
0449: throw new RuntimeException(
0450: "Unable to query database about " + query + ": "
0451: + e.getMessage());
0452: }
0453:
0454: // done
0455: return result;
0456: }
0457:
0458: /**
0459: * Slurps all attributes from related to a mapping into a map.
0460: *
0461: * @param id is the reference id to slurp from as string. Especially
0462: * Postgres's indexing mechanism goes from tables scans to btrees, if
0463: * the numeric key is represented as a string. Strings should be safe
0464: * for other databases, too.
0465: * @return a Map with the attributes, which may be empty.
0466: */
0467: private Map attributes(String id) throws SQLException {
0468: Map result = new TreeMap();
0469:
0470: // sanity checks
0471: if (id == null)
0472: return result;
0473:
0474: // parametrize
0475: PreparedStatement ps = getStatement(0);
0476: ps.setString(1, id);
0477:
0478: // slurp results
0479: ResultSet rs = ps.executeQuery();
0480: while (rs.next())
0481: result.put(rs.getString(1), rs.getString(2));
0482:
0483: // done
0484: rs.close();
0485: return result;
0486: }
0487:
0488: /**
0489: * Retrieves all entries for a given LFN from the replica catalog.
0490: * Each entry in the result set is a tuple of a PFN and all its
0491: * attributes.
0492: *
0493: * @param lfn is the logical filename to obtain information for.
0494: * @return a collection of replica catalog entries
0495: * @see ReplicaCatalogEntry
0496: */
0497: public Collection lookup(String lfn) {
0498: List result = new ArrayList();
0499: String query = mCStatements[1];
0500:
0501: // sanity check
0502: if (lfn == null)
0503: return result;
0504: if (mConnection == null)
0505: throw new RuntimeException(c_error);
0506:
0507: // start to ask
0508: try {
0509: PreparedStatement ps = getStatement(1);
0510: ps.setString(1, quote(lfn));
0511:
0512: ResultSet rs = ps.executeQuery();
0513: while (rs.next()) {
0514: result.add(new ReplicaCatalogEntry(rs.getString("pfn"),
0515: attributes(rs.getString("id"))));
0516: }
0517:
0518: rs.close();
0519: } catch (SQLException e) {
0520: throw new RuntimeException(
0521: "Unable to query database about " + query + ": "
0522: + e.getMessage());
0523: }
0524:
0525: // done
0526: return result;
0527: }
0528:
0529: /**
0530: * Retrieves all entries for a given LFN from the replica catalog.
0531: * Each entry in the result set is just a PFN string. Duplicates
0532: * are reduced through the set paradigm.
0533: *
0534: * @param lfn is the logical filename to obtain information for.
0535: * @return a set of PFN strings
0536: */
0537: public Set lookupNoAttributes(String lfn) {
0538: Set result = new TreeSet();
0539: String query = mCStatements[1];
0540:
0541: // sanity check
0542: if (lfn == null)
0543: return result;
0544: if (mConnection == null)
0545: throw new RuntimeException(c_error);
0546:
0547: // start to ask
0548: try {
0549: PreparedStatement ps = getStatement(1);
0550: ps.setString(1, quote(lfn));
0551:
0552: ResultSet rs = ps.executeQuery(query);
0553: while (rs.next())
0554: result.add(rs.getString("pfn"));
0555:
0556: rs.close();
0557: } catch (SQLException e) {
0558: throw new RuntimeException(
0559: "Unable to query database about " + query + ": "
0560: + e.getMessage());
0561: }
0562:
0563: // done
0564: return result;
0565: }
0566:
0567: /**
0568: * Retrieves multiple entries for a given logical filename, up to the
0569: * complete catalog. Retrieving full catalogs should be harmful, but
0570: * may be helpful in an online display or portal.
0571: *
0572: * @param lfns is a set of logical filename strings to look up.
0573: * @return a map indexed by the LFN. Each value is a collection
0574: * of replica catalog entries for the LFN.
0575: * @see org.griphyn.common.catalog.ReplicaCatalogEntry
0576: */
0577: public Map lookup(Set lfns) {
0578: Map result = new HashMap();
0579: String query = mCStatements[1];
0580:
0581: // sanity check
0582: if (lfns == null || lfns.size() == 0)
0583: return result;
0584: if (mConnection == null)
0585: throw new RuntimeException(c_error);
0586:
0587: try {
0588: ResultSet rs = null;
0589: PreparedStatement ps = getStatement(1);
0590: for (Iterator i = lfns.iterator(); i.hasNext();) {
0591: List value = new ArrayList();
0592: String lfn = (String) i.next();
0593: ps.setString(1, quote(lfn));
0594: rs = ps.executeQuery();
0595: while (rs.next()) {
0596: value.add(new ReplicaCatalogEntry(rs
0597: .getString("pfn"), attributes(rs
0598: .getString("id"))));
0599: }
0600: rs.close();
0601: result.put(lfn, value);
0602: }
0603: } catch (SQLException e) {
0604: throw new RuntimeException("Unable to query database with "
0605: + query + ": " + e.getMessage());
0606: }
0607:
0608: // done
0609: return result;
0610: }
0611:
0612: /**
0613: * Retrieves multiple entries for a given logical filename, up to the
0614: * complete catalog. Retrieving full catalogs should be harmful, but
0615: * may be helpful in an online display or portal.
0616: *
0617: * @param lfns is a set of logical filename strings to look up.
0618: * @return a map indexed by the LFN. Each value is a set
0619: * of PFN strings.
0620: */
0621: public Map lookupNoAttributes(Set lfns) {
0622: Map result = new HashMap();
0623: String query = mCStatements[1];
0624:
0625: // sanity check
0626: if (lfns == null || lfns.size() == 0)
0627: return result;
0628: if (mConnection == null)
0629: throw new RuntimeException(c_error);
0630:
0631: try {
0632: ResultSet rs = null;
0633: PreparedStatement ps = getStatement(1);
0634: for (Iterator i = lfns.iterator(); i.hasNext();) {
0635: Set value = new TreeSet();
0636: String lfn = (String) i.next();
0637: ps.setString(1, quote(lfn));
0638: rs = ps.executeQuery();
0639: while (rs.next())
0640: value.add(rs.getString("pfn"));
0641: rs.close();
0642: result.put(lfn, value);
0643: }
0644: } catch (SQLException e) {
0645: throw new RuntimeException("Unable to query database with "
0646: + query + ": " + e.getMessage());
0647: }
0648:
0649: // done
0650: return result;
0651: }
0652:
0653: /**
0654: * Retrieves multiple entries for a given logical filename, up to the
0655: * complete catalog. Retrieving full catalogs should be harmful, but
0656: * may be helpful in online display or portal.<p>
0657: *
0658: * @param lfns is a set of logical filename strings to look up.
0659: * @param handle is the resource handle, restricting the LFNs.
0660: * @return a map indexed by the LFN. Each value is a collection
0661: * of replica catalog entries (all attributes).
0662: * @see ReplicaCatalogEntry
0663: */
0664: public Map lookup(Set lfns, String handle) {
0665: Map result = new HashMap();
0666: int which = (handle == null ? 3 : 2);
0667: String query = mCStatements[which];
0668:
0669: // sanity check
0670: if (lfns == null || lfns.size() == 0)
0671: return result;
0672: if (mConnection == null)
0673: throw new RuntimeException(c_error);
0674:
0675: try {
0676: ResultSet rs = null;
0677: PreparedStatement ps = getStatement(which);
0678: ps.setString(2, quote(ReplicaCatalogEntry.RESOURCE_HANDLE));
0679: if (handle != null)
0680: ps.setString(3, quote(handle));
0681:
0682: for (Iterator i = lfns.iterator(); i.hasNext();) {
0683: List value = new ArrayList();
0684: String lfn = (String) i.next();
0685: ps.setString(1, quote(lfn));
0686: rs = ps.executeQuery();
0687: while (rs.next()) {
0688: value.add(new ReplicaCatalogEntry(rs
0689: .getString("pfn"), attributes(rs
0690: .getString("id"))));
0691: }
0692: rs.close();
0693: result.put(lfn, value);
0694: }
0695: } catch (SQLException e) {
0696: throw new RuntimeException("Unable to query database with "
0697: + query + ": " + e.getMessage());
0698: }
0699:
0700: // done
0701: return result;
0702: }
0703:
0704: /**
0705: * Retrieves multiple entries for a given logical filename, up to the
0706: * complete catalog. Retrieving full catalogs should be harmful, but
0707: * may be helpful in online display or portal.<p>
0708: *
0709: * @param lfns is a set of logical filename strings to look up.
0710: * @param handle is the resource handle, restricting the LFNs.
0711: * @return a map indexed by the LFN. Each value is a set of
0712: * physical filenames.
0713: */
0714: public Map lookupNoAttributes(Set lfns, String handle) {
0715: Map result = new HashMap();
0716: int which = (handle == null ? 3 : 2);
0717: String query = mCStatements[which];
0718:
0719: // sanity check
0720: if (lfns == null || lfns.size() == 0)
0721: return result;
0722: if (mConnection == null)
0723: throw new RuntimeException(c_error);
0724:
0725: try {
0726: ResultSet rs = null;
0727: PreparedStatement ps = getStatement(which);
0728: ps.setString(2, quote(ReplicaCatalogEntry.RESOURCE_HANDLE));
0729: if (handle != null)
0730: ps.setString(3, quote(handle));
0731:
0732: for (Iterator i = lfns.iterator(); i.hasNext();) {
0733: Set value = new TreeSet();
0734: String lfn = (String) i.next();
0735: ps.setString(1, quote(lfn));
0736: rs = ps.executeQuery();
0737: while (rs.next())
0738: value.add(rs.getString("pfn"));
0739: rs.close();
0740: result.put(lfn, value);
0741: }
0742: } catch (SQLException e) {
0743: throw new RuntimeException("Unable to query database with "
0744: + query + ": " + e.getMessage());
0745: }
0746:
0747: // done
0748: return result;
0749: }
0750:
0751: /**
0752: * Helper function to assemble various pieces.
0753: *
0754: * @param value is the value of the object from the map.
0755: * @param obj is the name of the table column
0756: * @param where is the decision, if we had a previous WHERE clause or not.
0757: * @see #lookup( Map )
0758: */
0759: private String addItem(Object value, String obj, boolean where) {
0760: // sanity check, no column can be NULL
0761: if (value == null)
0762: return new String();
0763:
0764: String v = (value instanceof String) ? (String) value : value
0765: .toString();
0766: StringBuffer q = new StringBuffer(80);
0767: q.append(where ? " AND " : " WHERE ");
0768: q.append(obj).append(" LIKE '").append(quote(v)).append('\'');
0769:
0770: return q.toString();
0771: }
0772:
0773: /**
0774: * Retrieves multiple entries for a given logical filename, up to the
0775: * complete catalog. Retrieving full catalogs should be harmful, but
0776: * may be helpful in online display or portal.
0777: *
0778: * @param constraints is mapping of keys 'lfn', 'pfn', or any
0779: * attribute name, e.g. the resource handle 'site', to a string that
0780: * has some meaning to the implementing system. This can be a SQL
0781: * wildcard for queries, or a regular expression for Java-based memory
0782: * collections. Unknown keys are ignored. Using an empty map requests
0783: * the complete catalog.
0784: * @return a map indexed by the LFN. Each value is a collection
0785: * of replica catalog entries.
0786: * @see ReplicaCatalogEntry
0787: */
0788: public Map lookup(Map constraints) {
0789: Map result = new TreeMap();
0790:
0791: // more sanity
0792: if (mConnection == null)
0793: throw new RuntimeException(c_error);
0794:
0795: // prepare statement
0796: boolean flag = false;
0797: boolean where = false;
0798: StringBuffer q = new StringBuffer(256);
0799: q
0800: .append("SELECT DISTINCT r.id,r.lfn,r.pfn FROM rc_lfn r, rc_attr a");
0801:
0802: for (Iterator i = constraints.keySet().iterator(); i.hasNext();) {
0803: String s, key = (String) i.next();
0804: if (key.equals("lfn")) {
0805: s = addItem(constraints.get("lfn"), "r.lfn", where);
0806: } else if (key.equals("pfn")) {
0807: s = addItem(constraints.get("pfn"), "r.pfn", where);
0808: } else {
0809: if (!flag) {
0810: q.append(where ? " AND " : " WHERE ").append(
0811: "r.id=a.id");
0812: flag = true;
0813: }
0814: s = addItem(constraints.get(key), "a." + key, where);
0815: }
0816: if (s.length() > 0) {
0817: where = true;
0818: q.append(s);
0819: }
0820: }
0821:
0822: // start to ask
0823: String lfn = null;
0824: ReplicaCatalogEntry pair = null;
0825: String query = q.toString();
0826: try {
0827: Statement st = mConnection.createStatement();
0828: ResultSet rs = st.executeQuery(query);
0829: while (rs.next()) {
0830: lfn = rs.getString("lfn");
0831: pair = new ReplicaCatalogEntry(rs.getString("pfn"),
0832: attributes(rs.getString("id")));
0833:
0834: // add list, if the LFN does not already exist
0835: if (!result.containsKey(lfn))
0836: result.put(lfn, new ArrayList());
0837:
0838: // now add to the list
0839: ((List) result.get(lfn)).add(pair);
0840: }
0841: rs.close();
0842: st.close();
0843: } catch (SQLException e) {
0844: throw new RuntimeException(
0845: "Unable to query database about " + query + ": "
0846: + e.getMessage());
0847: }
0848:
0849: // done
0850: return result;
0851: }
0852:
0853: /**
0854: * Lists all logical filenames in the catalog.
0855: *
0856: * @return A set of all logical filenames known to the catalog.
0857: */
0858: public Set list() {
0859: // short-cut
0860: return list(null);
0861: }
0862:
0863: /**
0864: * Lists a subset of all logical filenames in the catalog.
0865: *
0866: * @param constraint is a constraint for the logical filename only. It
0867: * is a string that has some meaning to the implementing system. This
0868: * can be a SQL wildcard for queries, or a regular expression for
0869: * Java-based memory collections.
0870: * @return A set of logical filenames that match. The set may be empty
0871: */
0872: public Set list(String constraint) {
0873: Set result = new TreeSet();
0874:
0875: // more sanity
0876: if (mConnection == null)
0877: throw new RuntimeException(c_error);
0878:
0879: // prepare statement
0880: // FIXME: work with pre-prepared statements
0881: String query = "SELECT lfn FROM rc_lfn";
0882: if (constraint != null && constraint.length() > 0)
0883: query += " WHERE lfn LIKE '" + quote(constraint) + "'";
0884:
0885: // start to ask
0886: try {
0887: Statement st = mConnection.createStatement();
0888: ResultSet rs = st.executeQuery(query);
0889: while (rs.next()) {
0890: result.add(rs.getString(0));
0891: }
0892: rs.close();
0893: st.close();
0894: } catch (SQLException e) {
0895: throw new RuntimeException(
0896: "Unable to query database about " + query + ": "
0897: + e.getMessage());
0898: }
0899:
0900: // done
0901: return result;
0902: }
0903:
0904: /**
0905: * Inserts a new mapping into the replica catalog.
0906: *
0907: * @param lfn is the logical filename under which to book the entry.
0908: * @param tuple is the physical filename and associated PFN attributes.
0909: *
0910: * @return number of insertions, should always be 1. On failure,
0911: * throw an exception, don't use zero.
0912: */
0913: public int insert(String lfn, ReplicaCatalogEntry tuple) {
0914: String query = "[no query]";
0915: int result = 0;
0916: boolean autoCommitWasOn = false;
0917: int state = 0;
0918:
0919: // sanity checks
0920: if (lfn == null || tuple == null)
0921: return result;
0922: if (mConnection == null)
0923: throw new RuntimeException(c_error);
0924:
0925: try {
0926: // delete-before-insert as one transaction
0927: if ((autoCommitWasOn = mConnection.getAutoCommit()))
0928: mConnection.setAutoCommit(false);
0929: state++; // state == 1
0930:
0931: // // delete before insert...
0932: // delete( lfn, tuple.getPFN() );
0933: state++; // state == 2
0934:
0935: ResultSet rs = null;
0936: Statement st = null;
0937: StringBuffer m = new StringBuffer(256);
0938: String id = null;
0939: if (!m_autoinc) {
0940: //
0941: // use sequences, no auto-generated keys possible
0942: //
0943: query = "SELECT nextval('rc_lfn_id')";
0944: st = mConnection.createStatement();
0945: rs = st.executeQuery(query);
0946: if (rs.next())
0947: id = rs.getString(1);
0948: else
0949: throw new SQLException(
0950: "Unable to access sequence generator");
0951: rs.close();
0952: st.close();
0953: state++; // state == 3
0954:
0955: m.append("INSERT INTO rc_lfn(id,lfn,pfn) VALUES('");
0956: m.append(id).append("','");
0957: m.append(quote(lfn)).append("','");
0958: m.append(quote(tuple.getPFN())).append("')");
0959: query = m.toString();
0960: st = mConnection.createStatement();
0961: result = st.executeUpdate(query); // ,Statement.RETURN_GENERATED_KEYS);
0962: st.close();
0963: state++; // state == 4
0964: } else {
0965: //
0966: // use autoinc columns, obtain autogenerated keys afterwards
0967: //
0968: m.append("INSERT INTO rc_lfn(lfn,pfn) VALUES('");
0969: m.append(quote(lfn)).append("','");
0970: m.append(quote(tuple.getPFN())).append("')");
0971: query = m.toString();
0972: st = mConnection.createStatement();
0973: result = st.executeUpdate(query,
0974: Statement.RETURN_GENERATED_KEYS);
0975: state++; // state == 3
0976:
0977: rs = st.getGeneratedKeys();
0978: if (rs.next())
0979: id = rs.getString(1);
0980: else
0981: throw new SQLException(
0982: "Unable to access autogenerated key");
0983: rs.close();
0984: st.close();
0985: state++; // state == 4
0986: }
0987:
0988: query = mCStatements[4];
0989: PreparedStatement ps = getStatement(4);
0990: // ps.setString( 1, id ); // GRRR, Pg8!!!
0991: ps.setLong(1, Long.parseLong(id));
0992:
0993: for (Iterator i = tuple.getAttributeIterator(); i.hasNext();) {
0994: String name = (String) i.next();
0995: Object value = tuple.getAttribute(name);
0996: ps.setString(2, name);
0997: if (value == null)
0998: ps.setNull(3, Types.VARCHAR);
0999: else
1000: ps.setString(3,
1001: value instanceof String ? (String) value
1002: : value.toString());
1003: ps.executeUpdate();
1004: }
1005: state++; // state == 5
1006:
1007: mConnection.commit();
1008: } catch (SQLException e) {
1009: try {
1010: if (state > 0 && state < 4)
1011: mConnection.rollback();
1012: } catch (SQLException e2) {
1013: // ignore rollback problems
1014: }
1015:
1016: throw new RuntimeException("Unable to tell database "
1017: + query + " (state=" + state + "): "
1018: + e.getMessage());
1019: } finally {
1020: // restore original auto-commit state
1021: try {
1022: if (autoCommitWasOn)
1023: mConnection.setAutoCommit(true);
1024: } catch (SQLException e) {
1025: // ignore
1026: }
1027: }
1028:
1029: return result;
1030: }
1031:
1032: /**
1033: * Inserts a new mapping into the replica catalog. This is a
1034: * convenience function exposing the resource handle. Internally, the
1035: * <code>ReplicaCatalogEntry</code> element will be contructed, and
1036: * passed to the appropriate insert function.
1037: *
1038: * @param lfn is the logical filename under which to book the entry.
1039: * @param pfn is the physical filename associated with it.
1040: * @param handle is a resource handle where the PFN resides.
1041: * @return number of insertions, should always be 1. On failure,
1042: * throw an exception, don't use zero.
1043: * @see #insert( String, ReplicaCatalogEntry )
1044: * @see ReplicaCatalogEntry
1045: */
1046: public int insert(String lfn, String pfn, String handle) {
1047: return insert(lfn, new ReplicaCatalogEntry(pfn, handle));
1048: }
1049:
1050: /**
1051: * Inserts multiple mappings into the replica catalog. The input is a
1052: * map indexed by the LFN. The value for each LFN key is a collection
1053: * of replica catalog entries.
1054: *
1055: * @param x is a map from logical filename string to list of replica
1056: * catalog entries.
1057: * @return the number of insertions.
1058: * @see org.griphyn.common.catalog.ReplicaCatalogEntry
1059: */
1060: public int insert(Map x) {
1061: int result = 0;
1062:
1063: // sanity checks
1064: if (x == null || x.size() == 0)
1065: return result;
1066: if (mConnection == null)
1067: throw new RuntimeException(c_error);
1068:
1069: // FIXME: Create a true bulk mode. This is inefficient, but will
1070: // get the job done (for now).
1071: Set lfns = x.keySet();
1072: for (Iterator i = lfns.iterator(); i.hasNext();) {
1073: String lfn = (String) i.next();
1074: List value = (List) x.get(lfn);
1075: if (value != null && value.size() > 0) {
1076: for (Iterator j = value.iterator(); j.hasNext();) {
1077: result += insert(lfn, (ReplicaCatalogEntry) j
1078: .next());
1079: }
1080: }
1081: }
1082:
1083: // done
1084: return result;
1085: }
1086:
1087: /**
1088: * Deletes multiple mappings into the replica catalog. The input is a
1089: * map indexed by the LFN. The value for each LFN key is a collection
1090: * of replica catalog entries. On setting matchAttributes to false, all entries
1091: * having matching lfn pfn mapping to an entry in the Map are deleted.
1092: * However, upon removal of an entry, all attributes associated with the pfn
1093: * also evaporate (cascaded deletion).
1094: *
1095: * @param x is a map from logical filename string to list of
1096: * replica catalog entries.
1097: * @param matchAttributes whether mapping should be deleted only if all
1098: * attributes match.
1099: *
1100: * @return the number of deletions.
1101: * @see ReplicaCatalogEntry
1102: */
1103: public int delete(Map x, boolean matchAttributes) {
1104: throw new java.lang.UnsupportedOperationException(
1105: "delete(Map,boolean) not implemented as yet");
1106: }
1107:
1108: /**
1109: * Deletes a specific mapping from the replica catalog. We don't care
1110: * about the resource handle. More than one entry could theoretically
1111: * be removed. Upon removal of an entry, all attributes associated
1112: * with the PFN also evaporate (cascading deletion).
1113: *
1114: * @param lfn is the logical filename in the tuple.
1115: * @param pfn is the physical filename in the tuple.
1116: * @return the number of removed entries.
1117: */
1118: public int delete(String lfn, String pfn) {
1119: int result = 0;
1120:
1121: // sanity checks
1122: if (lfn == null || pfn == null)
1123: return result;
1124: if (mConnection == null)
1125: throw new RuntimeException(c_error);
1126:
1127: // prepare statement
1128: // FIXME: work with pre-prepared statements
1129: StringBuffer m = new StringBuffer(256);
1130: m.append("DELETE FROM rc_lfn WHERE lfn='");
1131: m.append(quote(lfn)).append('\'');
1132: m.append(" AND pfn='").append(quote(pfn)).append('\'');
1133: String query = m.toString();
1134:
1135: try {
1136: Statement st = mConnection.createStatement();
1137: st.execute(query);
1138: result = st.getUpdateCount();
1139: st.close();
1140: } catch (SQLException e) {
1141: throw new RuntimeException("Unable to tell database "
1142: + query + ": " + e.getMessage());
1143: }
1144:
1145: // done
1146: return result;
1147: }
1148:
1149: /**
1150: * Deletes a very specific mapping from the replica catalog. The LFN
1151: * must be matches, the PFN, and all PFN attributes specified in the
1152: * replica catalog entry. More than one entry could theoretically be
1153: * removed. Upon removal of an entry, all attributes associated with
1154: * the PFN also evaporate (cascading deletion).
1155: *
1156: * @param lfn is the logical filename in the tuple.
1157: * @param tuple is a description of the PFN and its attributes.
1158: * @return the number of removed entries, either 0 or 1.
1159: */
1160: public int delete(String lfn, ReplicaCatalogEntry tuple) {
1161: int result = 0;
1162: String query = "[no query]";
1163:
1164: // sanity checks
1165: if (lfn == null || tuple == null)
1166: return result;
1167: if (mConnection == null)
1168: throw new RuntimeException(c_error);
1169:
1170: try {
1171: StringBuffer m = new StringBuffer(256);
1172: for (Iterator i = tuple.getAttributeIterator(); i.hasNext();) {
1173: String name = (String) i.next();
1174: Object value = tuple.getAttribute(name);
1175: m.append("SELECT id FROM rc_attr WHERE name='");
1176: m.append(quote(name)).append("' AND value");
1177: if (value == null)
1178: m.append(" IS NULL");
1179: else
1180: m.append("='").append(quote(value.toString()))
1181: .append('\'');
1182: if (i.hasNext())
1183: m.append(" INTERSECT ");
1184: }
1185: query = m.toString();
1186:
1187: m = new StringBuffer(256);
1188: m.append("DELETE FROM rc_lfn WHERE lfn='").append(
1189: quote(lfn));
1190: m.append("' AND pfn='").append(quote(tuple.getPFN()));
1191: m.append("' AND id=?");
1192:
1193: Statement st = mConnection.createStatement();
1194: ResultSet rs = st.executeQuery(query);
1195:
1196: query = m.toString();
1197: PreparedStatement ps = mConnection.prepareStatement(query);
1198: while (rs.next()) {
1199: ps.setString(1, rs.getString(1));
1200: result += ps.executeUpdate();
1201: }
1202: ps.close();
1203: rs.close();
1204: st.close();
1205: } catch (SQLException e) {
1206: throw new RuntimeException("Unable to tell database "
1207: + query + ": " + e.getMessage());
1208: }
1209:
1210: // done
1211: return result;
1212: }
1213:
1214: /**
1215: * Deletes all PFN entries for a given LFN from the replica catalog
1216: * where the PFN attribute is found, and matches exactly the object
1217: * value. This method may be useful to remove all replica entries that
1218: * have a certain MD5 sum associated with them. It may also be harmful
1219: * overkill.
1220: *
1221: * @param lfn is the logical filename to look for.
1222: * @param name is the PFN attribute name to look for.
1223: * @param value is an exact match of the attribute value to match.
1224: * @return the number of removed entries.
1225: */
1226: public int delete(String lfn, String name, Object value) {
1227: int result = 0;
1228: int which = value == null ? 9 : 8;
1229: String query = mCStatements[which];
1230:
1231: // sanity checks
1232: if (lfn == null || name == null)
1233: return result;
1234: if (mConnection == null)
1235: throw new RuntimeException(c_error);
1236:
1237: try {
1238: PreparedStatement ps = getStatement(which);
1239: ps.setString(1, quote(lfn));
1240: ps.setString(2, quote(name));
1241: if (value != null)
1242: ps.setString(3, quote(value.toString()));
1243: result = ps.executeUpdate();
1244: } catch (SQLException e) {
1245: throw new RuntimeException("Unable to tell database "
1246: + query + ": " + e.getMessage());
1247: }
1248:
1249: // done
1250: return result;
1251: }
1252:
1253: /**
1254: * Deletes all PFN entries for a given LFN from the replica catalog
1255: * where the resource handle is found. Karan requested this
1256: * convenience method, which can be coded like
1257: * <pre>
1258: * delete( lfn, RESOURCE_HANDLE, handle )
1259: * </pre>
1260: *
1261: * @param lfn is the logical filename to look for.
1262: * @param handle is the resource handle
1263: * @return the number of entries removed.
1264: */
1265: public int deleteByResource(String lfn, String handle) {
1266: return delete(lfn, ReplicaCatalogEntry.RESOURCE_HANDLE, handle);
1267: }
1268:
1269: /**
1270: * Removes all mappings for an LFN from the replica catalog.
1271: *
1272: * @param lfn is the logical filename to remove all mappings for.
1273: * @return the number of removed entries.
1274: */
1275: public int remove(String lfn) {
1276: int result = 0;
1277: String query = mCStatements[5];
1278:
1279: // sanity checks
1280: if (lfn == null)
1281: return result;
1282: if (mConnection == null)
1283: throw new RuntimeException(c_error);
1284:
1285: try {
1286: PreparedStatement ps = getStatement(5);
1287: ps.setString(1, quote(lfn));
1288: result = ps.executeUpdate();
1289: } catch (SQLException e) {
1290: throw new RuntimeException("Unable to tell database "
1291: + query + ": " + e.getMessage());
1292: }
1293:
1294: // done
1295: return result;
1296: }
1297:
1298: /**
1299: * Removes all mappings for a set of LFNs.
1300: *
1301: * @param lfns is a set of logical filename to remove all mappings for.
1302: * @return the number of removed entries.
1303: */
1304: public int remove(Set lfns) {
1305: int result = 0;
1306: String query = mCStatements[5];
1307:
1308: // sanity checks
1309: if (lfns == null || lfns.size() == 0)
1310: return result;
1311: if (mConnection == null)
1312: throw new RuntimeException(c_error);
1313:
1314: try {
1315: PreparedStatement ps = getStatement(5);
1316: for (Iterator i = lfns.iterator(); i.hasNext();) {
1317: ps.setString(1, quote((String) i.next()));
1318: result += ps.executeUpdate();
1319: }
1320: } catch (SQLException e) {
1321: throw new RuntimeException("Unable to tell database "
1322: + query + ": " + e.getMessage());
1323: }
1324:
1325: // done
1326: return result;
1327: }
1328:
1329: /**
1330: * Removes all entries from the replica catalog where the PFN attribute
1331: * is found, and matches exactly the object value.
1332: *
1333: * @param name is the PFN attribute name to look for.
1334: * @param value is an exact match of the attribute value to match.
1335: * @return the number of removed entries.
1336: */
1337: public int removeByAttribute(String name, Object value) {
1338: int result = 0;
1339: int which = value == null ? 7 : 6;
1340: String query = mCStatements[which];
1341:
1342: // sanity checks
1343: if (mConnection == null)
1344: throw new RuntimeException(c_error);
1345:
1346: try {
1347: PreparedStatement ps = getStatement(which);
1348: ps.setString(1, quote(name));
1349: if (value != null)
1350: ps.setString(2, value.toString());
1351: result = ps.executeUpdate();
1352: } catch (SQLException e) {
1353: throw new RuntimeException("Unable to tell database "
1354: + query + ": " + e.getMessage());
1355: }
1356:
1357: // done
1358: return result;
1359: }
1360:
1361: /**
1362: * Removes all entries associated with a particular resource handle.
1363: * This is useful, if a site goes offline. It is a convenience method,
1364: * which calls the generic <code>removeByAttribute</code> method.
1365: *
1366: * @param handle is the site handle to remove all entries for.
1367: * @return the number of removed entries.
1368: * @see #removeByAttribute( String, Object )
1369: */
1370: public int removeByAttribute(String handle) {
1371: return removeByAttribute(ReplicaCatalogEntry.RESOURCE_HANDLE,
1372: handle);
1373: }
1374:
1375: /**
1376: * Removes everything. Use with caution!
1377: *
1378: * @return the number of removed entries.
1379: */
1380: public int clear() {
1381: int result = 0;
1382:
1383: // sanity checks
1384: if (mConnection == null)
1385: throw new RuntimeException(c_error);
1386:
1387: // prepare statement
1388: String query = "DELETE FROM lfn_rc";
1389: try {
1390: Statement st = mConnection.createStatement();
1391: st.execute(query);
1392: result = st.getUpdateCount();
1393: st.close();
1394: } catch (SQLException e) {
1395: throw new RuntimeException("Unable to tell database "
1396: + query + ": " + e.getMessage());
1397: }
1398:
1399: // done
1400: return result;
1401: }
1402: }
|