edu.lternet.pasta.dml.DataManager.java Source code

Java tutorial

Introduction

Here is the source code for edu.lternet.pasta.dml.DataManager.java

Source

/**
 *    '$RCSfile: DataManager.java,v $'
 *
 *     '$Author: leinfelder $'
 *       '$Date: 2008-02-28 23:40:53 $'
 *   '$Revision: 1.34 $'
 *
 *  For Details: http://kepler.ecoinformatics.org
 *
 * Copyright (c) 2003 The Regents of the University of California.
 * All rights reserved.
 * 
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 * 
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 * 
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY
 * OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT,
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */

package edu.lternet.pasta.dml;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.lternet.pasta.dml.database.ConnectionNotAvailableException;
import edu.lternet.pasta.dml.database.DatabaseAdapter;
import edu.lternet.pasta.dml.database.DatabaseConnectionPoolInterface;
import edu.lternet.pasta.dml.database.DatabaseHandler;
import edu.lternet.pasta.dml.database.HSQLAdapter;
import edu.lternet.pasta.dml.database.OracleAdapter;
import edu.lternet.pasta.dml.database.PostgresAdapter;
import edu.lternet.pasta.dml.database.Query;
import edu.lternet.pasta.dml.database.TableMonitor;
import edu.lternet.pasta.dml.database.Union;
import edu.lternet.pasta.dml.download.DownloadHandler;
import edu.lternet.pasta.dml.download.DataStorageInterface;
import edu.lternet.pasta.dml.download.EcogridEndPointInterface;
import edu.lternet.pasta.dml.parser.Attribute;
import edu.lternet.pasta.dml.parser.AttributeList;
import edu.lternet.pasta.dml.parser.DataPackage;
import edu.lternet.pasta.dml.parser.Entity;
import edu.lternet.pasta.dml.parser.generic.DataPackageParserInterface;
import edu.lternet.pasta.dml.parser.generic.Eml200DataPackageParser;
import edu.lternet.pasta.dml.quality.QualityCheck;
import edu.lternet.pasta.dml.quality.QualityReport;
import edu.lternet.pasta.dml.quality.QualityCheck.Status;

/**
 * 
 * The DataManager class is the controlling class for the library. It exposes
 * the high-level API to the calling application.
 * 
 * There are six use-cases that this library supports:
 * 
 * 1. Parse the metadata to get at its entities and attributes.
 * 2. Download a data table from a remote site, using the URL in the metadata.
 * 3. Load a data table into the database table cache.
 * 4. Query a data table with a SQL select statement.
 * 5. Set an upper limit on the size of the database table cache.
 * 6. Set an expiration policy on a table in the database table cache.
 *
 */
public class DataManager {

    /*
     * Class fields
     */

    public static Log log = LogFactory.getLog(DataManager.class);

    /* Holds the singleton object for this class */
    private static DataManager dataManager = null;
    private static String databaseAdapterName = null;
    private static DatabaseConnectionPoolInterface connectionPool = null;

    // Constants
    private static final String BLANKSTR = "";
    private static final int MAXIMUM_NUMBER_TO_ACCESS_CONNECTIONPOOL = 10;
    private static final int SLEEP_TIME = 2000;

    /*
     * Constructors
     */

    /*
     * This is singleton class, so constructor is private
     */

    private DataManager(DatabaseConnectionPoolInterface connectionPool, String databaseAdapterName) {
        DataManager.connectionPool = connectionPool;
        DataManager.databaseAdapterName = databaseAdapterName;
    }

    /*
     * Class methods
     */

    /**
     * Gets the singleton instance of this class.
     * 
     * @return  the single instance of the DataManager class.
     */
    static public DataManager getInstance(DatabaseConnectionPoolInterface connectionPool,
            String databaseAdapterName) {
        if (dataManager == null) {
            dataManager = new DataManager(connectionPool, databaseAdapterName);
        } else if (DataManager.databaseAdapterName != null
                && !DataManager.databaseAdapterName.equals(databaseAdapterName)) {
            dataManager = new DataManager(connectionPool, databaseAdapterName);
        }

        return dataManager;
    }

    /*
     * Gets DBConnection from connection pool. If no connection available, it will
     * sleep and try again. If ceiling times reachs, null will return.
     * 
     */
    public static Connection getConnection() throws SQLException {
        Connection connection = null;
        int index = 0;
        if (connectionPool == null) {
            throw new SQLException("The Connection Pool is null");
        }
        while (index < MAXIMUM_NUMBER_TO_ACCESS_CONNECTIONPOOL) {
            try {
                connection = connectionPool.getConnection();
                break;
            } catch (ConnectionNotAvailableException cna) {
                try {
                    Thread.sleep(SLEEP_TIME);
                } catch (Exception e) {
                    log.error("Error in DataManager.getConnection(): " + e.getMessage());
                }
            } catch (SQLException sql) {
                log.error("Error in DataManager.getConnection(): " + sql.getMessage());
            }

            index++;
        }

        return connection;
    }

    /**
     * Returns checked out connection to connection pool.
     * 
     * @param  connection the Connection to be returned to the pool
     */
    public static void returnConnection(Connection connection) {
        connectionPool.returnConnection(connection);
    }

    /**
     * Get the value of the databaseAdapterName field.
     * 
     * @return  the value of the databaseAdapterName field
     */
    public static String getDatabaseAdapterName() {
        return databaseAdapterName;
    }

    /**
     * Gets the object of the database connection pool
     *  @return the object of dababase connection pool
     */
    public static DatabaseConnectionPoolInterface getDatabaseConnectionPool() {
        return connectionPool;
    }

    /*
     * Instance methods
     */

    /**
     * Create a database view from one or more entities in an entity list.
     * 
     * @param  ANSISQL    ANSI SQL string to create the view.
     * @param  entityList Array of entities whose table names and attribute
     *         names are used in creating the view.
     * @return a boolean value indicating the success of the create view 
     *         operation. True will be returned if successful, else false
     *         will be returned.
     */
    public boolean createDataView(String ANSISQL, Entity[] entityList) {
        boolean success = true;

        return success;
    }

    /**
     * Downloads all entities in a data package using the calling application's 
     * data storage interface. This allows the calling application to manage its 
     * data store in its own way. This version of the method downloads all 
     * entities in the entity list of the data package. This method implements
     * Use Case #2.
     * 
     * @param  dataPackage the data package containing a list of entities
     * @param  endPointInfo which provides ecogrid endpoint information
     * @param  dataStorageList the destination (data storage) of the downloading
     * @return a boolean value indicating the success of the download operation.
     *         true if successful, else false.
     */
    public boolean downloadData(DataPackage dataPackage, EcogridEndPointInterface endPointInfo,
            DataStorageInterface[] dataStorageList) {
        boolean success = true;
        Entity[] entities = dataPackage.getEntityList();

        for (int i = 0; i < entities.length; i++) {
            Entity entity = entities[i];
            success = downloadData(entity, endPointInfo, dataStorageList) && success;
        }

        return success;
    }

    /**
     * Downloads a single entity using the calling application's data storage
     * interface. This allows the calling application to manage its data store
     * in its own way. This method implements Use Case #2. 
     * 
     * This version of the method sets preserveFormat to false. This is
     * the Data Manager Library's original default behavior: gzip, zip,
     * or tar files are expanded when downloaded.
     * 
     * @param  entity the entity whose data is to be downloaded
     * @param  endPointInfo which provides ecogrid endpoint information
     * @param  dataStorageList the destination (data storage) of the downloading
     * @return a boolean value indicating the success of the download operation.
     *         true if successful, else false.
     */
    public boolean downloadData(Entity entity, EcogridEndPointInterface endPointInfo,
            DataStorageInterface[] dataStorageList) {
        boolean preserveFormat = false;
        return downloadData(entity, endPointInfo, dataStorageList, preserveFormat);
    }

    /**
     * Downloads a single entity using the calling application's data storage
     * interface. This allows the calling application to manage its data store
     * in its own way. This method implements Use Case #2.
     * 
     * This newer version of the method accepts a boolean value, preserveFormat.
     * Setting preserveFormat to false invokes the Data Manager Library's 
     * original default behavior: an object with a compressionMethod of 'gzip'
     * or 'zip' or an encodingMethod of 'tar' is expanded when downloaded.
     * However, when set to true, the object is downloaded with its original format 
     * preserved.
     * 
     * @param  entity the entity whose data is to be downloaded
     * @param  endPointInfo which provides ecogrid endpoint information
     * @param  dataStorageList the destination (data storage) of the downloading
     * @param  preserveFormat when set to true, do not unzip or un-tar the entity
     * @return a boolean value indicating the success of the download operation.
     *         true if successful, else false.
     */
    public boolean downloadData(Entity entity, EcogridEndPointInterface endPointInfo,
            DataStorageInterface[] dataStorageList, boolean preserveFormat) {
        log.debug(String.format("***** Downloading data for: %s, entity: %s\n", entity.getPackageId(),
                entity.getName()));
        DownloadHandler downloadHandler = entity.getDownloadHandler(endPointInfo, preserveFormat);
        boolean success = false;

        if (downloadHandler != null) {
            try {
                success = downloadHandler.download(dataStorageList);
            } catch (Exception e) {
                log.error("Error downloading entity name '" + entity.getName() + "': " + e.getMessage());
                success = false;
            }
        }

        return success;
    }

    /**
     * Downloads data from an input stream using the calling application's data 
     * storage interface. This allows the calling application to manage its 
     * data store in its own way. The metadata input stream first needs to be
     * parsed and a data package created from it. Then, all entities in the data
     * package are downloaded. This method implements Use Case #2.
     * 
     * @param  metadataInputStream an input stream to the metadata. 
     * @param  endPointInfo which provides ecogrid endpoint information
     * @param  dataStorageList the destination (data storage) of the downloading
     * @return a boolean value indicating the success of the download operation.
     *         true if successful, else false.
     */
    public boolean downloadData(InputStream metadataInputStream, EcogridEndPointInterface endPointInfo,
            DataStorageInterface[] dataStorageList) throws Exception {
        boolean success = false;
        DataPackage dataPackage = parseMetadata(metadataInputStream);

        if (dataPackage != null) {
            success = downloadData(dataPackage, endPointInfo, dataStorageList);
        }

        return success;
    }

    /**
     * Drops all tables in a data package. This is simply a pass-through to the
     * DatabaseHandler class.
     * 
     * @param dataPackage  the DataPackage object whose tables are to be dropped
     * @return true if successful, else false
     * @throws ClassNotFoundException
     * @throws SQLException
     * @throws Exception
     */
    public boolean dropTables(DataPackage dataPackage) throws ClassNotFoundException, SQLException, Exception {
        boolean success;

        DatabaseHandler databaseHandler = new DatabaseHandler(databaseAdapterName);
        success = databaseHandler.dropTables(dataPackage);

        return success;
    }

    /**
     * Drops all tables in a data package based on a packageId value. This is 
     * simply a pass-through to the DatabaseHandler class.
     * 
     * @param  packageId  the packageId associated with tables are to be dropped
     * @return true if successful, else false
     * @throws ClassNotFoundException
     * @throws SQLException
     * @throws Exception
     */
    public boolean dropTables(String packageId) throws ClassNotFoundException, SQLException, Exception {
        boolean success;

        DatabaseHandler databaseHandler = new DatabaseHandler(databaseAdapterName);
        success = databaseHandler.dropTables(packageId);

        return success;
    }

    /**
     * Creates each table described in the datapackage
     * @param dataPackage
     * @return boolean indicating success or failure of the operation
     * @throws SQLException
     * @throws Exception
     */
    public boolean createTables(DataPackage dataPackage) throws SQLException, Exception {
        boolean success;

        DatabaseHandler databaseHandler = new DatabaseHandler(databaseAdapterName);
        success = databaseHandler.generateTables(dataPackage);

        return success;
    }

    /**
     * Gets the database field name for a given entity attribute. First, we
     * ask the Attribute object for its dbFieldName value. If it doesn't know,
     * we check to see if it persists in the database.
     * 
     * @param   entity  the Entity that holds this attribute
     * @param   attribute  the Attribute whose database field name we want to get
     * @return  dbFieldName the database field name for this attribute, or null
     *          if no database field name can be found
     */
    public static String getDBFieldName(Entity entity, Attribute attribute) throws SQLException {
        String dbFieldName = null;

        // First, access the dbFieldName directly from the attribute object
        dbFieldName = attribute.getDBFieldName();

        /*
         * If the attribute doesn't contain a value, get it from the database
         * field names stored in the entity's database table.
         */
        if (dbFieldName == null || dbFieldName.trim().equals(BLANKSTR)) {

            if (entity != null && attribute != null) {
                Attribute[] attributeArray = entity.getAttributes();
                String packageID = entity.getPackageId();
                String entityName = entity.getName();
                String[] dbFieldNames = getDBFieldNames(packageID, entityName);

                if (attributeArray.length == dbFieldNames.length) {
                    for (int i = 0; i < attributeArray.length; i++) {
                        Attribute arrayAttribute = attributeArray[i];
                        if (attribute.equals(arrayAttribute)) {
                            dbFieldName = dbFieldNames[i];
                        }
                    }
                }
            }
        }

        return dbFieldName;
    }

    /**
     * Gets a list of database field names for the specified packageID and entity
     * name. This is a pass-through to the same method in the TableMonitor class.
     * 
     * @param packageID    the packageID for this entity
     * @param entityName   the entity name
     * @return  a String array holding the field names for this entity, or null
     *          if there was no match for this packageID and entity name in the
     *          database.
     * @throws SQLException
     */
    public static String[] getDBFieldNames(String packageID, String entityName) throws SQLException {
        String[] dbFieldNames = null;

        DatabaseAdapter dbAdapter = getDatabaseAdapterObject(databaseAdapterName);
        TableMonitor tableMonitor = new TableMonitor(dbAdapter);
        dbFieldNames = tableMonitor.getDBFieldNames(packageID, entityName);

        return dbFieldNames;
    }

    /**
     * Gets the database table name for a specified packageID and entity name.
     * This is a pass-through to the same method in the TableMonitor class.
     * 
     * @param packageID    the packageID for this entity
     * @param entityName   the entity name
     * @return dbTableName the database table name for this entity, or null if
     *                     no match to the packageID and entity name is found
     * @throws SQLException
     */
    public static String getDBTableName(String packageID, String entityName) throws SQLException {
        String dbTableName = null;

        DatabaseAdapter dbAdapter = getDatabaseAdapterObject(databaseAdapterName);
        TableMonitor tableMonitor = new TableMonitor(dbAdapter);
        dbTableName = tableMonitor.getDBTableName(packageID, entityName);

        return dbTableName;
    }

    /**
     * Gets the database table name for a specified Entity.
     * This is an alternative signature that uses an Entity object instead of
     * string parameters. First we ask the Entity to tell us its table name,
     * but if it doesn't know, we check to see whether the table name for this
     * entity is persistent in the database.
     * 
     * @param entity       the Entity object whose table name is being determined
     * @return dbTableName the database table name for this entity, or null if
     *                     no match to the packageID and entity name is found
     * @throws SQLException
     */
    public static String getDBTableName(Entity entity) throws SQLException {
        String dbTableName = null;

        // First, try to get the dbTableName directly from the Entity object
        if (entity != null) {
            dbTableName = entity.getDBTableName();

            /* If the entity doesn't know its dbTableName value, use the entity's
             * packageID and name fields to query the database for the entity's
             * table_name field value.
             */
            if (dbTableName == null || dbTableName.trim().equals(BLANKSTR)) {
                String packageID = entity.getPackageId();
                String entityName = entity.getName();
                dbTableName = DataManager.getDBTableName(packageID, entityName);
            }
        }

        return dbTableName;
    }

    /**
     * Loads all entities in a data package to the database table cache. This
     * method implements Use Case #3.
     * 
     * @param  dataPackage the data package containing a list of entities whose
     *         data is to be loaded into the database table cache.
     * @param  endPointInfo which provides ecogrid endpoint information
     * @return a boolean value indicating the success of the load-data operation.
     *         true if successful, else false.
     */
    public boolean loadDataToDB(DataPackage dataPackage, EcogridEndPointInterface endPointInfo)
            throws ClassNotFoundException, SQLException, Exception {
        boolean success = true;
        Entity[] entities = dataPackage.getEntityList();

        for (int i = 0; i < entities.length; i++) {
            success = loadDataToDB(entities[i], endPointInfo) && success;
        }

        return success;
    }

    /**
     * Loads data from a single entity into the database table cache.
     * This method implements Use Case #3.
     * 
     * @param  entity  the entity whose data is to be loaded into the database 
     *                 table cache.
     * @param  endPointInfo which provides ecogrid endpoint information
     * @return a boolean value indicating the success of the load-data operation.
     *         true if successful, else false.
     */
    public boolean loadDataToDB(Entity entity, EcogridEndPointInterface endPointInfo)
            throws ClassNotFoundException, SQLException, Exception {
        boolean success = false;

        log.debug(String.format("***** Loading data to DB for: %s, entity: %s\n", entity.getPackageId(),
                entity.getName()));
        // Initialize the dataLoadQualityCheck
        String qualityCheckIdentifier = "dataLoadStatus";
        QualityCheck qualityCheckTemplate = QualityReport.getQualityCheckTemplate(qualityCheckIdentifier);
        QualityCheck dataLoadQualityCheck = new QualityCheck(qualityCheckIdentifier, qualityCheckTemplate);

        /*
         * otherEntity is allowed to optionally omit the attributeList element.
         * If this is an otherEntity and its attributeList is null, or it is
         * non-null but it contains an empty attribute array, return success.
         */
        if ((entity != null) && (entity.isOtherEntity())) {
            AttributeList attributeList = entity.getAttributeList();
            if (attributeList == null) {
                success = true;
            } else {
                Attribute[] attributes = attributeList.getAttributes();
                if (attributes == null) {
                    success = true;
                }
            }

            if (success) {
                // Create an informational quality check stating that the data load
                // was not attempted for this otherEntity entity.
                if (QualityCheck.shouldRunQualityCheck(entity, dataLoadQualityCheck)) {
                    dataLoadQualityCheck.setFound("Data loading was not attempted for this 'otherEntity' "
                            + "because no attribute list was found in the EML");
                    dataLoadQualityCheck
                            .setExplanation("In EML, a data entity of type 'otherEntity' is not required "
                                    + "to document an attribute list");
                    entity.addQualityCheck(dataLoadQualityCheck);
                }
            }
        }
        /*
         * Do not attempt to load data into a database table if the entity
         * does not have a distribution online and has either distribution
         * offline or inline.
         */
        else if ((entity != null) && !entity.hasDistributionOnline()
                && (entity.hasDistributionOffline() || entity.hasDistributionInline())) {
            success = true;
            if (QualityCheck.shouldRunQualityCheck(entity, dataLoadQualityCheck)) {
                dataLoadQualityCheck.setFound("Data loading was not attempted for this entity "
                        + "because its distribution is 'inline' or 'offline'");
                dataLoadQualityCheck.setExplanation(
                        "Unable to process data entities with distribution " + "set to 'inline' or 'offline'");
                entity.addQualityCheck(dataLoadQualityCheck);
            }
        } else {
            try {
                DatabaseHandler databaseHandler = new DatabaseHandler(databaseAdapterName);

                // First, generate a table for the entity
                success = databaseHandler.generateTable(entity);

                // If we have a table, then load the data for the entity.
                if (success) {
                    success = databaseHandler.loadDataToDB(entity, endPointInfo);

                    // If the data could not be loaded to the database, drop the table.
                    if (!success) {
                        databaseHandler.dropTable(entity);
                    }
                }
            } catch (SQLException e) {
                success = false;
            } finally {
            }
        }

        return success;
    }

    /**
     * Loads all entities in a data package to the database table cache. This
     * version of the method is passed a metadata input stream that needs
     * to be parsed. Then, all entities in the data package are loaded to the
     * database table cache. This method implements Use Case #3.
     * 
     * @param  metadataInputStream the metadata input stream to be parsed into
     *         a DataPackage object.
     * @param  endPointInfo which provides ecogrid endpoint information
     * @return a boolean value indicating the success of the load-data operation.
     *         true if successful, else false.
     */
    public boolean loadDataToDB(InputStream metadataInputStream, EcogridEndPointInterface endPointInfo)
            throws Exception {
        boolean success = false;

        DataPackage dataPackage = parseMetadata(metadataInputStream);

        if (dataPackage != null) {
            success = loadDataToDB(dataPackage, endPointInfo);
        }

        return success;
    }

    /**
     * Parses metadata using the appropriate parser object. The return value is
     * a DataPackage object containing the parsed metadata. This method
     * implements Use Case #1.
     * 
     * @param metadataInputStream  an input stream to the metadata to be parsed.
     * @return a DataPackage object containing the parsed metadata
     */
    public DataPackage parseMetadata(InputStream metadataInputStream) throws Exception {
        DataPackage dataPackage = null;
        DataPackageParserInterface parser = new Eml200DataPackageParser();

        parser.parse(metadataInputStream);
        dataPackage = parser.getDataPackage();
        dataPackageQuality(dataPackage);

        return dataPackage;
    }

    /**
     * Parses metadata using the passed parser parameter. The return value is
     * a DataPackage object containing the parsed metadata. This method
     * implements Use Case #1.
     * 
     * @param metadataInputStream  an input stream to the metadata to be parsed.
     * @param genericParser the appropriate parser implementation for the metadataInputStream
     * @return a DataPackage object containing the parsed metadata
     * 
     * @throws Exception
     */
    public DataPackage parseMetadata(InputStream metadataInputStream, DataPackageParserInterface genericParser)
            throws Exception {

        DataPackage dataPackage = null;
        genericParser.parse(metadataInputStream);
        dataPackage = genericParser.getDataPackage();
        dataPackageQuality(dataPackage);

        return dataPackage;
    }

    /*
     * If quality reporting is enabled, runs quality checks on the
     * data package and stores the results in its QualityReport
     * object.
     */
    private void dataPackageQuality(DataPackage dataPackage) {
        // Initialize the duplicateEntityName quality check
        String duplicateEntityIdentifier = "duplicateEntityName";
        QualityCheck duplicateEntityTemplate = QualityReport.getQualityCheckTemplate(duplicateEntityIdentifier);
        QualityCheck duplicateEntityQualityCheck = new QualityCheck(duplicateEntityIdentifier,
                duplicateEntityTemplate);
        if (QualityCheck.shouldRunQualityCheck(dataPackage, duplicateEntityQualityCheck)) {
            String duplicateName = dataPackage.findDuplicateEntityName();
            boolean hasDuplicate = (duplicateName != null);

            if (hasDuplicate) {
                duplicateEntityQualityCheck.setFound("Found duplicate entity name: " + duplicateName);
                duplicateEntityQualityCheck.setFailedStatus();
            } else {
                duplicateEntityQualityCheck.setFound("No duplicates found");
                duplicateEntityQualityCheck.setStatus(Status.valid);
            }
            dataPackage.addDatasetQualityCheck(duplicateEntityQualityCheck);
        }
    }

    /**
     * Runs a database query on one or more data packages. This method
     * implements Use Case #4.
     * 
     * @param query    A Query java object hold query information.
     * @param packages The data packages holding the entities to be queried. 
     *                 Metadata about the data types of the attributes being
     *                 queried is contained in these data packages.
     * @return A ResultSet object holding the query results.
     */
    public ResultSet selectData(Query query, DataPackage[] packages)
            throws ClassNotFoundException, SQLException, Exception {

        DatabaseHandler databaseHandler;
        ResultSet resultSet = null;

        try {
            databaseHandler = new DatabaseHandler(databaseAdapterName);
            String ANSISQL = query.toSQLString();
            resultSet = databaseHandler.selectData(ANSISQL, packages);
        } finally {
        }

        return resultSet;
    }

    /**
    * Runs a database query on one or more metadata input streams. Each of the
    * metadata input streams needs to first be parsed, creating a list of data 
    * packages. The data packages contain entities, and the entities hold metadata
    * about the data types of the attributes being queried. This method 
    * implements Use Case #4.
    * 
    * @param query    A Query java object hold query information.
    * @param emlInputStreams An array of input streams that need to be parsed 
    *                 into a list of data packages. The data packages hold the 
    *                 lists of entities to be queried. Metadata about the data 
    *                 types of the attributes in the select statement is 
    *                 contained in these data packages.
    * @return A ResultSet object holding the query results.
    */
    public ResultSet selectData(Query query, InputStream[] emlInputStreams) throws Exception {
        DataPackage[] packages = new DataPackage[emlInputStreams.length];
        ResultSet resultSet = null;

        for (int i = 0; i < emlInputStreams.length; i++) {
            DataPackage dataPackage = parseMetadata(emlInputStreams[i]);
            packages[i] = dataPackage;
        }

        resultSet = selectData(query, packages);

        return resultSet;
    }

    public ResultSet selectData(Union union, DataPackage[] packages)
            throws ClassNotFoundException, SQLException, Exception {

        DatabaseHandler databaseHandler;
        ResultSet resultSet = null;

        try {
            databaseHandler = new DatabaseHandler(databaseAdapterName);
            String ANSISQL = union.toSQLString();
            resultSet = databaseHandler.selectData(ANSISQL, packages);
        } finally {
        }

        return resultSet;
    }

    /**
     * Runs a database query on a view. The view must already exist in the
     * database (see createDataView() method).
     * 
     * @param  ANSISQL  A string holding the ANSI SQL selection syntax.
     * @return A ResultSet object holding the query results.
     */
    public ResultSet selectDataFromView(String ANSISQL) {
        ResultSet resultSet = null;

        return resultSet;
    }

    /**
     * Set the String value of the databaseAdapterName field.
     * 
     * This method should probably throw an exception if the value does not
     * match any members of the recognized set of database adapter names.
     * 
     * @param databaseAdapterName
     */
    public void setDatabaseAdapterName(String databaseAdapterName) {
        DataManager.databaseAdapterName = databaseAdapterName;
    }

    /**
     * Sets an upper limit on the size of the database table cache. If the limit
     * is about to be exceeded, the TableMonitor will attempt to free up space
     * by deleting old tables from the table cache. This method implements
     * Use Case #5.
     * 
     * @param size The upper limit, in MB, on the size of the database table
     *        cache.
     */
    public void setDatabaseSize(int size) throws SQLException, ClassNotFoundException {
        try {
            DatabaseAdapter dbAdapter = getDatabaseAdapterObject(databaseAdapterName);
            TableMonitor tableMonitor = new TableMonitor(dbAdapter);
            tableMonitor.setDBSize(size);
        } finally {
        }
    }

    /**
     * Sets the expiration policy on a table in the database table cache. The
     * policy is an enumerated integer value indicating whether this table can
     * be expired from the cache. (The precise meaning of these values is yet to
     * be determined.) This method implements Use Case #6.
     * 
     * @param tableName the name of the table whose expiration policy is being
     *                  set
     * @param policy    an enumerated integer value indicating whether the table
     *                  should be expired from the datbase table cache. (The
     *                  precise meaning of this value is yet to be determined.)
     */
    public void setTableExpirationPolicy(String tableName, int policy) throws SQLException, ClassNotFoundException {

        try {
            DatabaseAdapter dbAdapter = getDatabaseAdapterObject(databaseAdapterName);
            TableMonitor tableMonitor = new TableMonitor(dbAdapter);

            tableMonitor.setTableExpirationPolicy(tableName, policy);
        } finally {
        }
    }

    /** 
     * Constructs and returns a DatabaseAdapter object based on a given database 
     * adapter name.
     * 
     * @param dbAdapterName   Database adapter name, a string. It should match
     *                        one of the constants in the DatabaseAdapter class,
     *                        e.g. DatabaseAdapter.POSTGRES_ADAPTER. If no match
     *                        is made, returns null.
     */
    public static DatabaseAdapter getDatabaseAdapterObject(String dbAdapterName) {
        if (dbAdapterName == null) {
            return null;
        }
        if (dbAdapterName.equals(DatabaseAdapter.POSTGRES_ADAPTER)) {
            PostgresAdapter databaseAdapter = new PostgresAdapter();
            return databaseAdapter;
        } else if (dbAdapterName.equals(DatabaseAdapter.HSQL_ADAPTER)) {
            HSQLAdapter databaseAdapter = new HSQLAdapter();
            return databaseAdapter;
        } else if (dbAdapterName.equals(DatabaseAdapter.ORACLE_ADAPTER)) {
            OracleAdapter databaseAdapter = new OracleAdapter();
            return databaseAdapter;
        }

        return null;
    }

}