org.globus.workspace.persistence.PersistenceAdapterImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.globus.workspace.persistence.PersistenceAdapterImpl.java

Source

/*
 * Copyright 1999-2008 University of Chicago
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package org.globus.workspace.persistence;

import java.io.IOException;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.globus.workspace.Lager;
import org.globus.workspace.WorkspaceConstants;
import org.globus.workspace.async.AsyncRequest;
import org.globus.workspace.creation.IdempotentInstance;
import org.globus.workspace.creation.IdempotentReservation;
import org.globus.workspace.creation.defaults.IdempotentInstanceImpl;
import org.globus.workspace.creation.defaults.IdempotentReservationImpl;
import org.globus.workspace.network.Association;
import org.globus.workspace.network.AssociationEntry;
import org.globus.workspace.persistence.impls.*;
import org.globus.workspace.async.backfill.Backfill;
import org.globus.workspace.scheduler.defaults.ResourcepoolEntry;
import org.globus.workspace.service.CoschedResource;
import org.globus.workspace.service.GroupResource;
import org.globus.workspace.service.InstanceResource;
import org.globus.workspace.service.binding.vm.FileCopyNeed;
import org.globus.workspace.service.binding.vm.VirtualMachine;
import org.globus.workspace.service.binding.vm.VirtualMachinePartition;
import org.nimbustools.api._repr._SpotPriceEntry;
import org.nimbustools.api.repr.CannotTranslateException;
import org.nimbustools.api.repr.ReprFactory;
import org.nimbustools.api.repr.SpotPriceEntry;
import org.nimbustools.api.services.rm.DoesNotExistException;
import org.nimbustools.api.services.rm.ManageException;

public class PersistenceAdapterImpl implements WorkspaceConstants, PersistenceAdapterConstants, PersistenceAdapter {

    // -------------------------------------------------------------------------
    // STATIC VARIABLES
    // -------------------------------------------------------------------------

    private static final Log logger = LogFactory.getLog(PersistenceAdapterImpl.class.getName());

    private static final int[] EMPTY_INT_ARRAY = new int[0];

    // -------------------------------------------------------------------------
    // INSTANCE VARIABLES
    // -------------------------------------------------------------------------

    private final Lager lager;
    private final DataSource dataSource;
    private final boolean dbTrace;
    private final ReprFactory repr;

    // caches, todo: ehcache
    private Hashtable associations;

    // -------------------------------------------------------------------------
    // CONSTRUCTOR
    // -------------------------------------------------------------------------

    public PersistenceAdapterImpl(DataSource dataSourceImpl, Lager lagerImpl, DBLoader loader, ReprFactory reprImpl)
            throws Exception {

        if (dataSourceImpl == null) {
            throw new IllegalArgumentException("dataSource may not be null");
        }
        this.dataSource = dataSourceImpl;

        if (lagerImpl == null) {
            throw new IllegalArgumentException("lager may not be null");
        }
        this.lager = lagerImpl;
        this.dbTrace = lagerImpl.dbLog;

        if (reprImpl == null) {
            throw new IllegalArgumentException("reprImpl may not be null");
        }
        this.repr = reprImpl;

        if (loader == null) {
            throw new IllegalArgumentException("loader may not be null");
        }
        if (!loader.isLoaded()) {
            throw new Exception("DBLoader reporting not loaded (?)");
        }

        try {
            this.prepareStatements();
        } catch (SQLException sql) {
            throw new Exception("Problem preparing DB statements: ", sql);
        }
    }

    // -------------------------------------------------------------------------
    // SETUP
    // -------------------------------------------------------------------------

    /**
     * This moves significant prepared statement setup times to service
     * initialization instead of the first time they're used.
     *
     * Documentation states that PreparedStatement caches are per pool
     * connection but testing indicates with Derby (in embedded mode),
     * this is OK using one connection.
     *
     * @throws SQLException problem
     */
    private void prepareStatements() throws SQLException {

        if (this.dbTrace) {
            logger.debug("prepareStatements()");
        }
        long mstart = 0;
        if (this.lager.perfLog) {
            mstart = System.currentTimeMillis();
        }

        //String[] ins =
        //        PersistenceAdapterConstants.INSENSITIVE_PREPARED_STATEMENTS;

        final String[] pstmts = PREPARED_STATEMENTS;

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();

            //for (int i = 0; i < ins.length; i++) {
            //    pstmt = c.prepareStatement(ins[i],
            //                               ResultSet.TYPE_SCROLL_INSENSITIVE,
            //                               ResultSet.CONCUR_UPDATABLE);
            //    pstmt.close();
            //}

            for (int i = 0; i < pstmts.length; i++) {
                pstmt = c.prepareStatement(pstmts[i]);
                pstmt.close();
            }

            pstmt = null;

        } catch (SQLException e) {
            logger.error("", e);
            throw e;
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }

            if (this.lager.perfLog) {
                final long mstop = System.currentTimeMillis();
                logger.debug("_perf: prepareStatements() took " + Long.toString(mstop - mstart) + " ms");
            }
        }
    }

    /* ********* */
    /* DB access */
    /* ********* */

    public void setState(int id, int state, Throwable t) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setState(): " + Lager.id(id) + ", state = " + state);
        }

        final byte[] faultBytes;

        try {
            faultBytes = ErrorUtil.toByteArray(t);
        } catch (IOException e) {
            throw new WorkspaceDatabaseException(e);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_STATE);
            pstmt.setInt(1, state);
            if (faultBytes != null) {
                pstmt.setObject(2, faultBytes, Types.BLOB);
            } else {
                pstmt.setNull(2, Types.BLOB);
            }
            pstmt.setInt(3, id);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setTargetState(int id, int targetState) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setTargetState(): " + Lager.id(id) + ", targetState = " + targetState);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_TARGET_STATE);
            pstmt.setInt(1, targetState);
            pstmt.setInt(2, id);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setOpsEnabled(int id, boolean enabled) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setOpsEnabled(): " + Lager.id(id) + ", enabled = " + enabled);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_OPS_ENABLED);
            if (enabled) {
                pstmt.setInt(1, 1);
            } else {
                pstmt.setInt(1, 0);
            }
            pstmt.setInt(2, id);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setNetwork(int id, String network) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setNetwork(): " + Lager.id(id) + ", network = " + network);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_NETWORKING);
            if (network != null) {
                pstmt.setString(1, network);
            } else {
                pstmt.setNull(1, Types.VARCHAR);
            }
            pstmt.setInt(2, id);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setVMMaccessOK(int resourceID, boolean accessOK)

            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setVMMaccessOK(): " + Lager.id(resourceID) + ", OK? " + accessOK);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_VMM_ACCESS_OK);
            if (accessOK) {
                pstmt.setInt(1, 1);
            } else {
                pstmt.setInt(1, 0);
            }
            pstmt.setInt(2, resourceID);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(resourceID) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setHostname(int id, String hostname) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setHostname(): " + Lager.id(id) + ", hostname = " + hostname);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_HOSTNAME);
            if (hostname != null) {
                pstmt.setString(1, hostname);
            } else {
                pstmt.setNull(1, Types.VARCHAR);
            }
            pstmt.setInt(2, id);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setRootUnpropTarget(int id, String path)

            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setRootUnpropTarget(): " + Lager.id(id) + ", path = " + path);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();

            // we need two SQL statements because unprop is known to be needed
            // if setting path, but unknown if clearing it
            if (path == null) {
                pstmt = c.prepareStatement(SQL_UNSET_ROOT_UNPROP_TARGET);
                pstmt.setNull(1, Types.VARCHAR);
                pstmt.setInt(2, id);
            } else {
                pstmt = c.prepareStatement(SQL_SET_ROOT_UNPROP_TARGET);
                pstmt.setString(1, path);
                pstmt.setInt(2, id);
            }

            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void addCustomizationNeed(int id, FileCopyNeed need) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("addFileCopyNeed(): " + Lager.id(id));
        }

        if (need == null) {
            throw new IllegalArgumentException("need may not be null");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_INSERT_FILE_COPY);

            pstmt.setInt(1, id);
            pstmt.setString(2, need.sourcePath);
            pstmt.setString(3, need.destPath);
            if (need.onImage()) {
                pstmt.setInt(4, 1);
            } else {
                pstmt.setInt(4, 0);
            }

            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setFileCopyOnImage(int id, FileCopyNeed need) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setFileCopyOnImage(): " + Lager.id(id) + ", on image = " + need.onImage());
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_FILE_COPY_ON_IMAGE);
            if (need.onImage()) {
                pstmt.setInt(1, 1);
            } else {
                pstmt.setInt(1, 0);
            }
            pstmt.setInt(2, id);
            pstmt.setString(3, need.sourcePath);
            pstmt.setString(4, need.destPath);

            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setStartTime(int id, Calendar startTime)

            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setStartTime(): " + Lager.id(id) + ", startTime = " + startTime);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_STARTTIME);

            if (startTime != null) {
                pstmt.setObject(1, new Long(startTime.getTimeInMillis()));
            } else {
                pstmt.setInt(1, 0);
            }

            pstmt.setInt(2, id);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void setTerminationTime(int id, Calendar termTime)

            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("setTerminationTime(): " + Lager.id(id) + ", startTime = " + termTime);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SET_TERMTIME);

            if (termTime != null) {
                pstmt.setObject(1, new Long(termTime.getTimeInMillis()));
            } else {
                pstmt.setInt(1, 0);
            }

            pstmt.setInt(2, id);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + ": updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void remove(int id, InstanceResource resource) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("remove(): " + Lager.id(id) + ", resource = " + resource);
        }

        if (id < 0) {
            throw new WorkspaceDatabaseException("id is less than zero");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        PreparedStatement[] pstmts = null;
        try {
            c = getConnection();
            c.setAutoCommit(false);
            pstmt = c.prepareStatement(SQL_DELETE_RESOURCE);
            pstmt.setInt(1, id);
            int numMod = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.id(id) + " resources: removed " + numMod + " rows");
            }

            if (resource.getVM() != null) {
                pstmts = VirtualMachinePersistenceUtil.getRemoveVM(resource.getVM(), id, c);
            }

            if (pstmts != null) {
                for (int i = 0; i < pstmts.length; i++) {
                    numMod = pstmts[i].executeUpdate();

                    if (this.dbTrace) {
                        logger.trace(Lager.id(id) + " vm record: removed " + numMod + " rows");
                    }
                }
            }

            c.commit();

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (pstmts != null) {
                    for (int i = 0; i < pstmts.length; i++) {
                        pstmts[i].close();
                    }
                }
                if (c != null) {
                    c.setAutoCommit(true);
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void removeGroup(String id)

            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("removeGroup(): " + Lager.groupid(id));
        }
        this.removeGroupOrEnsemble(id);
    }

    public void removeEnsemble(String id)

            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("removeEnsemble(): " + Lager.ensembleid(id));
        }
        this.removeGroupOrEnsemble(id);
    }

    private void removeGroupOrEnsemble(String id)

            throws WorkspaceDatabaseException {

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_DELETE_GROUP_RESOURCE);
            pstmt.setString(1, id);
            final int removed = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace(Lager.groupid(id) + " groupresources: removed " + removed + " rows");
            }
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public int[] findActiveWorkspacesIDs() throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("findActiveWorkspacesIDs()");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_RESOURCES);
            rs = pstmt.executeQuery();

            final ArrayList results = new ArrayList();
            if (rs == null || !rs.next()) {
                return null;
            } else {
                do {
                    final Integer id = new Integer(rs.getInt(1));
                    results.add(id);
                    if (this.dbTrace) {
                        logger.trace("found id: " + id);
                    }
                } while (rs.next());
            }

            // can't use toArray without converting to Integer[]
            final int[] ret = new int[results.size()];
            for (int i = 0; i < ret.length; i++) {
                ret[i] = ((Number) results.get(i)).intValue();
            }
            return ret;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public boolean isActiveWorkspaceID(int id)

            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("isActiveWorkspaceID(), id = " + id);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_LOAD_RESOURCE_NAME);
            pstmt.setInt(1, id);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                logger.debug("No workspace with id " + id);
                return false;
            } else {
                final String name = rs.getString(1);
                if (this.dbTrace) {
                    logger.trace("found id " + id + ", name = " + name);
                }
                return true;
            }
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public int[] findVMsInGroup(String groupID)

            throws WorkspaceDatabaseException {

        if (groupID == null) {
            throw new WorkspaceDatabaseException("groupID is null");
        }

        return this.findVMsInGroupOrEnsemble(groupID, SQL_SELECT_ALL_VMS_IN_GROUP);
    }

    public int[] findVMsInEnsemble(String ensembleID)

            throws WorkspaceDatabaseException {

        if (ensembleID == null) {
            throw new WorkspaceDatabaseException("ensembleID is null");
        }

        return this.findVMsInGroupOrEnsemble(ensembleID, SQL_SELECT_ALL_VMS_IN_ENSEMBLE);
    }

    private int[] findVMsInGroupOrEnsemble(String ID, String pstmtString)

            throws WorkspaceDatabaseException {

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(pstmtString);
            pstmt.setString(1, ID);

            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                logger.debug("no VM ensemble/group found with ID = " + ID);
                return EMPTY_INT_ARRAY;
            }

            final ArrayList vmidsList = new ArrayList(64);
            do {
                vmidsList.add(new Integer(rs.getInt(1)));
            } while (rs.next());

            // can't use toArray without converting to Integer[]
            final int[] ret = new int[vmidsList.size()];
            for (int i = 0; i < ret.length; i++) {
                ret[i] = ((Number) vmidsList.get(i)).intValue();
            }
            return ret;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public int[] findVMsByOwner(String ownerID)

            throws WorkspaceDatabaseException {

        if (ownerID == null) {
            throw new WorkspaceDatabaseException("ownerID is missing");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(SQL_SELECT_ALL_VMS_BY_OWNER);
            pstmt.setString(1, ownerID);

            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                logger.debug("no VMs found with ID = " + ownerID);
                return EMPTY_INT_ARRAY;
            }

            final ArrayList vmidsList = new ArrayList(64);
            do {
                vmidsList.add(new Integer(rs.getInt(1)));
            } while (rs.next());

            // can't use toArray without converting to Integer[]
            final int[] ret = new int[vmidsList.size()];
            for (int i = 0; i < ret.length; i++) {
                ret[i] = ((Number) vmidsList.get(i)).intValue();
            }
            return ret;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void add(InstanceResource resource)

            throws WorkspaceDatabaseException {

        if (resource == null) {
            throw new WorkspaceDatabaseException("resource is null");
        }

        final int id = resource.getID();

        if (id < 0) {
            throw new WorkspaceDatabaseException("id is less than zero");
        }

        if (this.dbTrace) {
            logger.trace("add(): " + Lager.id(id) + ", WorkspaceResource = " + resource);
        }

        final byte[] faultBytes;

        try {
            faultBytes = ErrorUtil.toByteArray(resource.getStateThrowable());
        } catch (IOException e) {
            throw new WorkspaceDatabaseException(e);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        PreparedStatement[] pstmts = null;
        try {
            c = getConnection();
            c.setAutoCommit(false);
            pstmt = c.prepareStatement(SQL_INSERT_RESOURCE);

            pstmt.setInt(1, id);
            pstmt.setString(2, resource.getName());
            pstmt.setInt(3, resource.getState());
            pstmt.setInt(4, resource.getTargetState());

            if (resource.getTerminationTime() != null) {
                pstmt.setObject(5, new Long(resource.getTerminationTime().getTimeInMillis()));
            } else {
                pstmt.setInt(5, 0);
            }

            if (resource.isOpsEnabled()) {
                pstmt.setInt(6, 1);
            } else {
                pstmt.setInt(6, 0);
            }

            if (resource.getCreatorID() != null) {
                pstmt.setString(7, resource.getCreatorID());
            } else {
                pstmt.setNull(7, Types.VARCHAR);
            }

            if (resource.getStartTime() != null) {
                pstmt.setObject(8, new Long(resource.getStartTime().getTimeInMillis()));
            } else {
                pstmt.setInt(8, 0);
            }

            if (resource.isVMMaccessOK()) {
                pstmt.setInt(9, 1);
            } else {
                pstmt.setInt(9, 0);
            }

            if (resource.getEnsembleId() != null) {
                pstmt.setString(10, resource.getEnsembleId());
            } else {
                pstmt.setNull(10, Types.VARCHAR);
            }

            if (resource.getGroupId() != null) {
                pstmt.setString(11, resource.getGroupId());
            } else {
                pstmt.setNull(11, Types.VARCHAR);
            }

            pstmt.setInt(12, resource.getGroupSize());

            if (resource.isLastInGroup()) {
                pstmt.setInt(13, 1);
            } else {
                pstmt.setInt(13, 0);
            }

            pstmt.setInt(14, resource.getLaunchIndex());

            if (faultBytes != null) {
                pstmt.setObject(15, faultBytes, Types.BLOB);
            } else {
                pstmt.setNull(15, Types.BLOB);
            }

            pstmt.setString(16, resource.getClientToken());

            pstmt.setDouble(17, resource.getChargeRatio());

            if (this.dbTrace) {
                logger.trace("creating WorkspaceResource db " + "entry for " + Lager.id(id));
            }

            pstmt.executeUpdate();

            if (resource instanceof VMPersistence) {

                pstmts = VirtualMachinePersistenceUtil.getInsertVM(resource, id, c);

                if (this.dbTrace) {
                    logger.trace("creating VirtualMachine db " + "entry for " + Lager.id(id) + ": " + pstmts.length
                            + " inserts");
                }

                for (int i = 0; i < pstmts.length; i++) {
                    pstmts[i].executeUpdate();
                }
            }

            c.commit();

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } catch (ManageException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (pstmts != null) {
                    for (int i = 0; i < pstmts.length; i++) {
                        pstmts[i].close();
                    }
                }
                if (c != null) {
                    c.setAutoCommit(true);
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void addGroup(GroupResource resource)

            throws WorkspaceDatabaseException {

        if (resource == null) {
            throw new IllegalArgumentException("resource is null");
        }

        final String id = resource.getID();

        if (this.dbTrace) {
            logger.trace("addGroup(): " + Lager.groupid(id));
        }

        this.addGroupOrEnsemble(id, resource.getCreatorID());
    }

    public void addEnsemble(CoschedResource resource)

            throws WorkspaceDatabaseException {

        if (resource == null) {
            throw new IllegalArgumentException("resource is null");
        }

        final String id = resource.getID();

        if (this.dbTrace) {
            logger.trace("addEnsemble(): " + Lager.ensembleid(id));
        }

        this.addGroupOrEnsemble(id, resource.getCreatorID());
    }

    private void addGroupOrEnsemble(String id, String creatorDN)

            throws WorkspaceDatabaseException {

        if (id == null) {
            throw new IllegalArgumentException("id is null");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_INSERT_GROUP_RESOURCE);

            pstmt.setString(1, id);

            if (creatorDN != null) {
                pstmt.setString(2, creatorDN);
            } else {
                pstmt.setNull(2, Types.VARCHAR);
            }

            pstmt.executeUpdate();

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    /**
     * @param id id
     * @param resource resource
     * @throws DoesNotExistException
     * @throws WorkspaceDatabaseException
     */
    public void load(int id, InstanceResource resource) throws DoesNotExistException, WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("load(): " + Lager.id(id) + ", WorkspaceResource = " + resource);
        }

        if (id < 0) {
            throw new DoesNotExistException("id is less than zero");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        PreparedStatement[] pstmts = null;
        ResultSet rs = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(SQL_LOAD_RESOURCE);
            pstmt.setInt(1, id);
            rs = pstmt.executeQuery();
            if (rs == null || !rs.next()) {
                final String err = "resource with id = " + id + " not found";
                logger.debug(err);
                throw new DoesNotExistException(err);
            } else {
                final String name = rs.getString(1);
                resource.setName(name);
                final int state = rs.getInt(2);
                final int targetState = rs.getInt(3);
                resource.setInitialTargetState(targetState);
                final long t = rs.getLong(4);
                if (t == 0) {
                    resource.setTerminationTime(null);
                } else {
                    final Calendar term = Calendar.getInstance();
                    term.setTimeInMillis(t);
                    resource.setTerminationTime(term);
                }
                final boolean opsEnabled = rs.getBoolean(5);
                resource.setInitialOpsEnabled(opsEnabled);

                final String dn = rs.getString(6);
                resource.setCreatorID(dn);

                final long s = rs.getLong(7);
                if (s == 0) {
                    resource.setStartTime(null);
                } else {
                    final Calendar start = Calendar.getInstance();
                    start.setTimeInMillis(s);
                    resource.setStartTime(start);
                }

                final boolean vmmAccessOK = rs.getBoolean(8);
                resource.setInitialVMMaccessOK(vmmAccessOK);

                final String ensembleid = rs.getString(9);
                resource.setEnsembleId(ensembleid);

                final String groupid = rs.getString(10);
                resource.setGroupId(groupid);

                final int groupsize = rs.getInt(11);
                resource.setGroupSize(groupsize);

                final boolean isLastInGroup = rs.getBoolean(12);
                resource.setLastInGroup(isLastInGroup);

                final int launchIndex = rs.getInt(13);
                resource.setLaunchIndex(launchIndex);

                final Blob errBlob = rs.getBlob(14);
                if (errBlob != null) {
                    // getBytes requires int, cast from long
                    final int length = (int) errBlob.length();
                    final Throwable err = ErrorUtil.getThrowable(errBlob.getBytes(1, length));
                    resource.setInitialState(state, err);
                } else {
                    resource.setInitialState(state, null);
                }

                final String clientToken = rs.getString(15);
                resource.setClientToken(clientToken);

                final double chargeRatio = rs.getDouble(16);
                resource.setChargeRatio(chargeRatio);

                if (this.dbTrace) {
                    logger.trace("found " + Lager.id(id) + ": name = " + name + ", state = " + state
                            + ", targetState = " + targetState + ", termination time = " + t + ", opsEnabled = "
                            + opsEnabled + ", creator ID = " + dn + ", start time = " + s + ", vmmAccessOK = "
                            + vmmAccessOK + ", ensembleid = " + ensembleid + ", groupid = " + groupid
                            + ", groupsize = " + groupsize + ", isLastInGroup = " + isLastInGroup
                            + ", launchIndex = " + launchIndex + ", clientToken = " + clientToken
                            + ", chargeRatio = " + chargeRatio + ", error present = " + (errBlob != null));
                }

                rs.close();

                if (resource instanceof VMPersistence) {
                    if (this.dbTrace) {
                        logger.trace(Lager.id(id) + ": load virtual machine");
                    }

                    pstmts = VirtualMachinePersistenceUtil.getVMQuery(id, c);

                    rs = pstmts[0].executeQuery();
                    if (rs == null || !rs.next()) {
                        logger.error("resource with id=" + id + " not found");
                        throw new DoesNotExistException();
                    }

                    final VirtualMachine vm = VirtualMachinePersistenceUtil.newVM(id, rs);

                    if (this.dbTrace) {
                        logger.trace(Lager.id(id) + ", created vm:\n" + vm.toString());
                    }

                    rs.close();

                    rs = pstmts[1].executeQuery();
                    if (rs == null || !rs.next()) {
                        logger.debug("resource with id=" + id + " has no" + " deployment information");
                    } else {
                        VirtualMachinePersistenceUtil.addDeployment(vm, rs);
                        if (this.dbTrace) {
                            logger.trace("added deployment info to vm object");
                        }
                        rs.close();
                    }

                    rs = pstmts[2].executeQuery();

                    if (rs == null || !rs.next()) {
                        logger.warn("resource with id=" + id + " has no" + " partitions");
                    } else {
                        final ArrayList partitions = new ArrayList(8);
                        do {
                            partitions.add(VirtualMachinePersistenceUtil.getPartition(rs));
                        } while (rs.next());

                        final VirtualMachinePartition[] parts = (VirtualMachinePartition[]) partitions
                                .toArray(new VirtualMachinePartition[partitions.size()]);
                        vm.setPartitions(parts);
                    }

                    rs = pstmts[3].executeQuery();

                    if (rs == null || !rs.next()) {
                        if (this.lager.dbLog) {
                            logger.debug("resource with id=" + id + " has no" + " customization needs");
                        }
                    } else {
                        do {
                            vm.addFileCopyNeed(VirtualMachinePersistenceUtil.getNeed(rs));
                        } while (rs.next());
                    }

                    ((VMPersistence) resource).setWorkspace(vm);
                }
            }
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } catch (DoesNotExistException e) {
            throw e;
        } catch (IOException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } catch (ClassNotFoundException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (pstmts != null) {
                    for (int i = 0; i < pstmts.length; i++) {
                        pstmts[i].close();
                    }
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public VirtualMachine loadVM(int id, Connection c)
            throws SQLException, DoesNotExistException, WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace(Lager.id(id) + ": load virtual machine");
        }

        PreparedStatement[] pstmts = VirtualMachinePersistenceUtil.getVMQuery(id, c);
        ResultSet rs = pstmts[0].executeQuery();
        if (rs == null || !rs.next()) {
            logger.error("resource with id=" + id + " not found");
            throw new DoesNotExistException();
        }

        final VirtualMachine vm = VirtualMachinePersistenceUtil.newVM(id, rs);

        if (this.dbTrace) {
            logger.trace(Lager.id(id) + ", created vm:\n" + vm.toString());
        }

        rs.close();

        rs = pstmts[1].executeQuery();
        if (rs == null || !rs.next()) {
            logger.debug("resource with id=" + id + " has no" + " deployment information");
        } else {
            VirtualMachinePersistenceUtil.addDeployment(vm, rs);
            if (this.dbTrace) {
                logger.trace("added deployment info to vm object");
            }
            rs.close();
        }

        rs = pstmts[2].executeQuery();

        if (rs == null || !rs.next()) {
            logger.warn("resource with id=" + id + " has no" + " partitions");
        } else {
            final ArrayList partitions = new ArrayList(8);
            do {
                partitions.add(VirtualMachinePersistenceUtil.getPartition(rs));
            } while (rs.next());

            final VirtualMachinePartition[] parts = (VirtualMachinePartition[]) partitions
                    .toArray(new VirtualMachinePartition[partitions.size()]);
            vm.setPartitions(parts);
        }

        rs = pstmts[3].executeQuery();

        if (rs == null || !rs.next()) {
            if (this.lager.dbLog) {
                logger.debug("resource with id=" + id + " has no" + " customization needs");
            }
        } else {
            do {
                vm.addFileCopyNeed(VirtualMachinePersistenceUtil.getNeed(rs));
            } while (rs.next());
        }

        return vm;
    }

    public void loadGroup(String id, GroupResource resource)

            throws DoesNotExistException, WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("loadGroup(): " + Lager.groupid(id) + ", WorkspaceGroupResource = " + resource);
        }

        final String callerID = this.loadGroupOrEnsemble(id);
        resource.setCreatorID(callerID);
        resource.setID(id);

        if (this.dbTrace) {
            logger.trace("found " + Lager.groupid(id) + ", creator ID = " + callerID);
        }
    }

    public void loadEnsemble(String id, CoschedResource resource)

            throws DoesNotExistException, WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("loadEnsemble(): " + Lager.ensembleid(id) + ", WorkspaceEnsembleResource = " + resource);
        }

        final String creatorID = this.loadGroupOrEnsemble(id);

        if (creatorID != null) {
            resource.setCreatorID(creatorID);
        }

        resource.setID(id);

        if (this.dbTrace) {
            logger.trace("found " + Lager.ensembleid(id) + ", creator ID = " + creatorID);
        }
    }

    private String loadGroupOrEnsemble(String id)

            throws DoesNotExistException, WorkspaceDatabaseException {

        if (id == null) {
            throw new DoesNotExistException("id is null");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(SQL_LOAD_GROUP_RESOURCE);
            pstmt.setString(1, id);
            rs = pstmt.executeQuery();
            if (rs == null || !rs.next()) {
                final String err = "resource with id = " + id + " not found";
                logger.error(err);
                throw new DoesNotExistException(err);
            } else {
                // could be null
                return rs.getString(1);
            }
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    /**
     * This is only called at factory initialization and not a priority
     * to switch to using PreparedStatements
     * 
     * @param assocs all associations (potentially merged with previous)
     * @throws WorkspaceDatabaseException
     */
    public void replaceAssocations(Hashtable assocs) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("replaceAssocations()");
        }

        final String[] inserts = AssociationPersistenceUtil.insertAllAssociationsSQL(assocs);

        if (inserts == null || inserts.length == 0) {
            logger.debug("no networks to persist");
            this.associations = null;
            return;
        }

        Connection c = null;
        Statement st = null;
        PreparedStatement pstmt = null;
        PreparedStatement pstmt2 = null;
        try {
            c = getConnection();
            c.setAutoCommit(false);

            // first delete all current rows
            pstmt = c.prepareStatement(SQL_DELETE_ALL_ASSOCIATIONS);
            final int removedNum = pstmt.executeUpdate();
            if (this.dbTrace) {
                logger.trace("removed " + removedNum + " entries from associations table");
            }

            pstmt2 = c.prepareStatement(SQL_DELETE_ALL_ASSOCIATION_ENTRIES);
            final int removedEntries = pstmt2.executeUpdate();
            if (this.dbTrace) {
                logger.trace("removed " + removedEntries + " entries from " + "association entries table");
            }

            st = c.createStatement();
            for (int i = 0; i < inserts.length; i++) {
                if (this.dbTrace) {
                    logger.trace("executing insert: '" + inserts[i] + "'");
                }
                st.executeUpdate(inserts[i]);
            }

            c.commit();

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (pstmt2 != null) {
                    pstmt2.close();
                }
                if (st != null) {
                    st.close();
                }
                if (c != null) {
                    c.setAutoCommit(true);
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }

        this.associations = assocs;
    }

    /**
     * For now, only in-use flag is replaceable.
     * @param name name
     * @param entry assoc entry
     */
    public void replaceAssociationEntry(String name, AssociationEntry entry) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("replaceAssociationEntry()");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = AssociationPersistenceUtil.updateEntryInUse(name, entry, c);
            pstmt.executeUpdate();
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }

    }

    public Hashtable currentAssociations() throws WorkspaceDatabaseException {
        return currentAssociations(true);
    }

    public synchronized Hashtable currentAssociations(boolean cachedIsFine) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("currentAssociations()");
        }

        if (cachedIsFine) {
            if (this.associations != null) {
                return this.associations;
            }
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        PreparedStatement pstmt2 = null;
        ResultSet rs = null;
        ResultSet rs2 = null;

        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_ALL_ASSOCIATIONS);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                if (lager.traceLog) {
                    logger.debug("no previous networks (associations)");
                }
                return new Hashtable();
            }

            Hashtable assocs = new Hashtable();
            do {
                // rs was next'd above already
                String name = rs.getString(1);
                String dns = rs.getString(2);
                long fileTime = rs.getLong(3);
                Association assoc = new Association(dns);
                assoc.setFileTime(fileTime);

                pstmt2 = c.prepareStatement(SQL_SELECT_ASSOCIATION);
                pstmt2.setString(1, name);
                rs2 = pstmt2.executeQuery();

                ArrayList entries = new ArrayList();
                while (rs2.next()) {
                    AssociationEntry entry = new AssociationEntry(rs2.getString(2), rs2.getString(3),
                            rs2.getString(4), rs2.getString(5), rs2.getString(6), rs2.getString(7));
                    entry.setInUse(rs2.getBoolean(8));

                    // Encoding that MAC is explicit in the MAC field itself.
                    // better to introduce a new field to schema?
                    String mac = entry.getMac();
                    if (mac.startsWith(AssociationPersistenceUtil.EXPLICIT_MAC_PREFIX)) {
                        mac = mac.substring(AssociationPersistenceUtil.EXPLICIT_MAC_PREFIX.length());
                        entry.setMac(mac);
                        entry.setExplicitMac(true);
                    }

                    entries.add(entry);
                }
                assoc.setEntries(entries);
                assocs.put(name, assoc);
                if (this.dbTrace) {
                    logger.trace("found previously stored network '" + name + "':\n" + assoc);
                }

                pstmt2.close();
                rs2.close();

            } while (rs.next());

            rs = null;
            rs2 = null;
            pstmt = null;
            pstmt2 = null;

            this.associations = assocs;

            return assocs;

        } catch (SQLException e) {
            this.associations = null;
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (pstmt2 != null) {
                    pstmt2.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (rs2 != null) {
                    rs2.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                this.associations = null;
                logger.error("SQLException in finally cleanup", sql);
            }
        }

    }

    public void updateResourcepoolEntryAvailableMemory(String hostname, int newAvailMemory, int preemptibleMemory)
            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("updateResourcepoolEntryAvailableMemory()");
        }

        if (hostname == null) {
            throw new IllegalArgumentException("hostname may not be null");
        }

        if (newAvailMemory < 0) {
            throw new IllegalArgumentException("newAvailMemory must be non-negative");
        }

        if (preemptibleMemory < 0) {
            throw new IllegalArgumentException("preemptibleMemory must be non-negative");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(SQL_UPDATE_RESOURCE_POOL_ENTRY_MEMORY);

            pstmt.setInt(1, newAvailMemory);
            pstmt.setInt(2, preemptibleMemory);
            pstmt.setString(3, hostname);

            final int updated = pstmt.executeUpdate();
            if (updated != 1) {
                throw new WorkspaceDatabaseException("expected row update");
            }
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }

    }

    public boolean updateResourcepoolEntry(String hostname, String pool, String networks, Integer memoryMax,
            Integer memoryAvail, Boolean active) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("updateResourcepoolEntry()");
        }
        if (hostname == null) {
            throw new IllegalArgumentException("hostname may not be null");
        }

        final StringBuilder sb = new StringBuilder();
        final List<Object> params = new ArrayList<Object>(5);
        if (pool != null) {
            appendUpdatePair(sb, "resourcepool");
            params.add(pool);
        }
        if (networks != null) {
            appendUpdatePair(sb, "associations");
            params.add(networks);
        }
        if (memoryMax != null) {
            appendUpdatePair(sb, "maximum_memory");
            params.add(memoryMax);
        }
        if (memoryAvail != null) {
            appendUpdatePair(sb, "available_memory");
            params.add(memoryAvail);
        }

        if (active != null) {
            appendUpdatePair(sb, "active");
            params.add(active);
        }
        if (params.isEmpty()) {
            throw new IllegalArgumentException("at least one updated field must be specified");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();

            final String q = String.format(SQL_UPDATE_RESOURCE_POOL_ENTRY_SKELETAL, sb.toString());
            if (this.dbTrace) {
                logger.trace("resourcepool_entry update query: " + q);
            }
            pstmt = c.prepareStatement(q);

            int paramIndex = 1;
            for (Object p : params) {
                pstmt.setObject(paramIndex, p);
                paramIndex++;
            }

            // add on the hostname param
            pstmt.setString(paramIndex, hostname);

            final int updated = pstmt.executeUpdate();
            return updated >= 1;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    private void appendUpdatePair(StringBuilder stringBuilder, String columnName) {
        if (stringBuilder.length() != 0) {
            stringBuilder.append(",");
        }
        stringBuilder.append(columnName).append("=?");
    }

    // one can only use result of this safely during service initialization
    public int memoryUsedOnPoolnode(String poolnode)

            throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("memoryUsedOnPoolnode(): poolnode = " + poolnode);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(SQL_JOIN_SELECT_RESOURCE_POOL_MEMORY);
            pstmt.setString(1, poolnode);
            rs = pstmt.executeQuery();

            if (rs == null) {
                if (this.dbTrace) {
                    logger.trace("memoryUsedOnPoolnode(): null result so " + "total is 0 MB");
                }
                return 0;
            }

            int total = 0;

            while (rs.next()) {
                int memory = rs.getInt(1);
                total += memory;
                if (this.dbTrace) {
                    logger.trace(
                            "memoryUsedOnPoolnode(): found " + memory + " MB for one VM, new total is " + total);
                }
            }

            if (this.dbTrace) {
                logger.trace("memoryUsedOnPoolnode(): final total = " + total);
            }
            return total;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public List<ResourcepoolEntry> currentResourcepoolEntries() throws WorkspaceDatabaseException {
        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_ALL_RESOURCE_POOL_ENTRIES);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                return Collections.emptyList();
            }

            List<ResourcepoolEntry> list = new ArrayList<ResourcepoolEntry>();

            do {
                final ResourcepoolEntry entry = new ResourcepoolEntry(rs.getString(1), rs.getString(2),
                        rs.getInt(4), rs.getInt(5), rs.getInt(7), rs.getString(3), rs.getBoolean(6));
                list.add(entry);
            } while (rs.next());

            return list;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }

    }

    public ResourcepoolEntry getResourcepoolEntry(String hostname) throws WorkspaceDatabaseException {

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_RESOURCE_POOL_ENTRY);
            pstmt.setString(1, hostname);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                return null;
            }

            return new ResourcepoolEntry(rs.getString(1), rs.getString(2), rs.getInt(4), rs.getInt(5), rs.getInt(7),
                    rs.getString(3), rs.getBoolean(6));

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void addResourcepoolEntry(ResourcepoolEntry entry) throws WorkspaceDatabaseException {

        if (entry == null) {
            throw new IllegalArgumentException("entry may not be null");
        }

        if (this.dbTrace) {
            logger.trace("addResourcepoolEntry(): hostname = " + entry.getHostname());
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(SQL_INSERT_RESOURCE_POOL_ENTRY);

            pstmt.setString(1, entry.getResourcePool());
            pstmt.setString(2, entry.getHostname());
            pstmt.setString(3, entry.getSupportedAssociations());
            pstmt.setInt(4, entry.getMemMax());
            pstmt.setInt(5, entry.getMemCurrent());
            pstmt.setInt(6, entry.isActive() ? 1 : 0);

            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace("Inserted " + updated + " row(s)");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public boolean removeResourcepoolEntry(String hostname) throws WorkspaceDatabaseException {
        if (hostname == null) {
            throw new IllegalArgumentException("hostname may not be null");
        }

        if (this.dbTrace) {
            logger.trace("removeResourcepoolEntry(): hostname = " + hostname);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_DELETE_RESOURCE_POOL_ENTRY);

            pstmt.setString(1, hostname);

            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace("Deleted " + updated + " row(s)");
            }

            return updated > 0;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    private Connection getConnection() throws SQLException {
        return this.dataSource.getConnection();
    }

    private static void returnConnection(Connection connection) {

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                logger.error("", e);
            }
        }
    }

    /* ******************************************************************** */

    // TEMPORARY:  for propagation implementation

    public void updateCursorPosition(long filepos) throws WorkspaceDatabaseException {

        final String SQL_UPDATE_CURSOR_POSITION = "UPDATE notification_position SET position=? WHERE 1=1";

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_UPDATE_CURSOR_POSITION);
            pstmt.setLong(1, filepos);
            pstmt.executeUpdate();
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public long currentCursorPosition() throws WorkspaceDatabaseException {

        final String SQL_INSERT_CURSOR_POSITION = "INSERT INTO notification_position VALUES(0)";

        final String SQL_SELECT_CURSOR_POSITION = "SELECT position FROM notification_position";

        Connection c = null;
        PreparedStatement pstmt = null;
        PreparedStatement pstmt2 = null;
        ResultSet rs = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_CURSOR_POSITION);

            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                pstmt2 = c.prepareStatement(SQL_INSERT_CURSOR_POSITION);
                pstmt2.executeUpdate();
                return 0;
            }

            return rs.getLong(1);

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (pstmt2 != null) {
                    pstmt2.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void updatePropagationCounter(int n) throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("updatePropagationCounter()");
        }

        final String SQL_SET_PROP_COUNTER = "UPDATE counter SET pending=? WHERE id=1";

        this.updateCounter(n, SQL_SET_PROP_COUNTER);

    }

    private void updateCounter(int n, String prepd) throws WorkspaceDatabaseException {

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(prepd);
            pstmt.setInt(1, n);
            pstmt.executeUpdate();
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public int readPropagationCounter() throws WorkspaceDatabaseException {

        if (this.dbTrace) {
            logger.trace("readPropagationCounter()");
        }

        final String SQL_SELECT_PROP_COUNTER = "SELECT pending FROM counter where id=1";

        return this.readCounter(1, SQL_SELECT_PROP_COUNTER);
    }

    private int readCounter(int n, String prepd) throws WorkspaceDatabaseException {

        final String SQL_INSERT_COUNTER = "INSERT INTO counter VALUES(?,?)";

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(prepd);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                pstmt.close();
                pstmt = c.prepareStatement(SQL_INSERT_COUNTER);
                pstmt.setInt(1, n);
                pstmt.setInt(2, 0);
                pstmt.executeUpdate();
                return 0;
            } else {
                return rs.getInt(1);
            }
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public boolean isInfeasibleRequest(int requestedMem) throws WorkspaceDatabaseException {

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_INFEASIBLE_MEMORY);
            pstmt.setInt(1, requestedMem);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                throw new WorkspaceDatabaseException("Should always have a result");
            } else {
                int numPossibleVmms = rs.getInt(1);
                if (numPossibleVmms > 0) {
                    return false;
                } else {
                    return true;
                }
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public List<ResourcepoolEntry> getAvailableEntriesSortedByFreeMemoryPercentage(int requestedMem,
            String resourcePool) throws WorkspaceDatabaseException {

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        ArrayList<ResourcepoolEntry> entries = new ArrayList<ResourcepoolEntry>();

        try {
            c = getConnection();
            if (resourcePool != null && resourcePool.length() > 0) {
                pstmt = c.prepareStatement(SQL_SELECT_AVAILABLE_ENTRIES_BY_POOL);
                pstmt.setInt(1, requestedMem);
                pstmt.setString(2, resourcePool);
            } else {
                pstmt = c.prepareStatement(SQL_SELECT_AVAILABLE_ENTRIES);
                pstmt.setInt(1, requestedMem);
            }
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                if (lager.traceLog) {
                    logger.debug("no available resource pool entries");
                }
            } else
                do {
                    // rs was next'd above already
                    String name = rs.getString(1);
                    String hostname = rs.getString(2);
                    String assocs = rs.getString(3);

                    if (hostname == null) {
                        logger.error("hostname cannot be null for resource pool entry");
                        continue;
                    }

                    if (assocs == null) {
                        logger.error("assocs cannot be null for resource pool entry");
                        continue;
                    }

                    ResourcepoolEntry entry = new ResourcepoolEntry(name, hostname, rs.getInt(4), rs.getInt(5),
                            rs.getInt(7), assocs, rs.getBoolean(6));
                    entries.add(entry);

                } while (rs.next());

            return entries;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public String[] getResourcePools() throws WorkspaceDatabaseException {
        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        ArrayList<String> entries = new ArrayList<String>();

        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_ALL_RESOURCE_POOLS);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                if (lager.traceLog) {
                    logger.debug("no available resource pool entries");
                }
            } else
                do {
                    // rs was next'd above already
                    String name = rs.getString(1);

                    entries.add(name);

                } while (rs.next());

            String[] zones = new String[entries.size()];
            entries.toArray(zones);
            return zones;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    private synchronized Integer getTotalMemory(String sql) throws WorkspaceDatabaseException {

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(sql);
            rs = pstmt.executeQuery();

            if (rs == null) {
                if (this.dbTrace) {
                    logger.trace("getTotalMemory(): null result so " + "total is 0 MB");
                }
                return 0;
            }

            Integer total = 0;

            if (rs.next()) {
                total = rs.getInt(1);
            }

            return total;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException e) {
                logger.error("SQLException in finally cleanup", e);
            }
        }
    }

    public Integer getTotalAvailableMemory(Integer multipleOf) throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("getTotalAvailableMemory(" + multipleOf + ")");
        }

        Integer total = 0;

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(SQL_SELECT_MULTIPLE_OF_AVAILABLE_MEMORY);
            pstmt.setInt(1, multipleOf);
            rs = pstmt.executeQuery();

            if (rs == null) {
                if (this.dbTrace) {
                    logger.trace("getTotalMemory(): null result so " + "total is 0 MB");
                }
                return 0;
            }

            if (rs.next()) {
                total = rs.getInt(1);
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException e) {
                logger.error("SQLException in finally cleanup", e);
            }
        }

        if (this.dbTrace) {
            logger.trace("getTotalAvailableMemory(" + multipleOf + "): total available memory = " + total);
        }

        return total;
    }

    public Integer getTotalAvailableMemory() throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("getTotalAvailableMemory()");
        }

        Integer total = getTotalMemory(SQL_SELECT_TOTAL_AVAILABLE_MEMORY);

        if (this.dbTrace) {
            logger.trace("getTotalAvailableMemory(): total max memory = " + total);
        }

        return total;
    }

    public Integer getTotalMaxMemory() throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("getTotalMaxMemory()");
        }

        Integer total = getTotalMemory(SQL_SELECT_TOTAL_MAX_MEMORY);

        if (this.dbTrace) {
            logger.trace("getTotalMaxMemory(): total max memory = " + total);
        }

        return total;
    }

    public Integer getTotalPreemptableMemory() throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("getTotalPreemptableMemory()");
        }

        Integer total = getTotalMemory(SQL_SELECT_TOTAL_PREEMPTABLE_MEMORY);

        if (this.dbTrace) {
            logger.trace("getTotalPreemptableMemory(): total pre-emptable memory = " + total);
        }

        return total;
    }

    public Integer getUsedNonPreemptableMemory() throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("getUsedNonPreemptableMemory()");
        }

        Integer total = getTotalMemory(SQL_SELECT_USED_NON_PREEMPTABLE_MEMORY);

        if (this.dbTrace) {
            logger.trace("getUsedNonPreemptableMemory(): used non pre-emptable memory = " + total);
        }

        return total;
    }

    public void addSpotPriceHistory(Calendar timeStamp, Double newPrice) throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("addSpotPriceHistory(): timeStamp = " + timeStamp + ", spot price = " + newPrice);
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_INSERT_SPOT_PRICE);

            if (timeStamp != null) {
                pstmt.setLong(1, new Long(timeStamp.getTimeInMillis()));
            } else {
                pstmt.setInt(1, 0);
            }

            pstmt.setDouble(2, newPrice);
            final int updated = pstmt.executeUpdate();

            if (this.dbTrace) {
                logger.trace("addSpotPriceHistory(): updated " + updated + " rows");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public List<SpotPriceEntry> getSpotPriceHistory(Calendar startDate, Calendar endDate)
            throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("getSpotPriceHistory() startDate: " + startDate == null ? null
                    : startDate.getTime() + ". endDate: " + endDate == null ? null : endDate.getTime());
        }

        Connection c = null;
        Statement st = null;
        ResultSet rs = null;

        try {
            c = getConnection();
            st = c.createStatement();

            String statement = SQL_SELECT_SPOT_PRICE;

            if (startDate != null || endDate != null) {
                statement += " WHERE ";

                if (startDate != null) {
                    statement += "tstamp >= " + startDate.getTimeInMillis();
                    if (endDate != null) {
                        statement += " AND";
                    }
                }

                if (endDate != null) {
                    statement += " tstamp <= " + endDate.getTimeInMillis();
                }
            }

            rs = st.executeQuery(statement);

            if (rs == null || !rs.next()) {
                if (lager.traceLog) {
                    logger.debug("no previous spot price history");
                }
                return new LinkedList<SpotPriceEntry>();
            }

            List<SpotPriceEntry> result = new LinkedList<SpotPriceEntry>();
            do {
                // rs was next'd above already
                Long timeMillis = rs.getLong(1);
                Double spotPrice = rs.getDouble(2);
                _SpotPriceEntry spotPriceEntry = repr._newSpotPriceEntry();

                Calendar timeStamp = Calendar.getInstance();
                timeStamp.setTimeInMillis(timeMillis);

                spotPriceEntry.setTimeStamp(timeStamp);
                spotPriceEntry.setSpotPrice(spotPrice);

                result.add(spotPriceEntry);

                if (this.dbTrace) {
                    logger.trace("found spot price entry: '" + timeStamp + " : " + spotPrice + "'");
                }
            } while (rs.next());

            return result;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (st != null) {
                    st.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public Double getLastSpotPrice() throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("getLastSpotPrice()");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_LAST_SPOT_PRICE);

            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                if (lager.traceLog) {
                    logger.debug("no previous spot price");
                }
                return null;
            }

            double result = rs.getDouble(1);

            if (rs.next()) {
                logger.warn("Wrong behavior: multiple spot prices from last time stamp.");
            }

            return result;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public Backfill getStoredBackfill() throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("getStoredBackfill()");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_SELECT_BACKFILL);

            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                if (lager.traceLog) {
                    logger.debug("no previous backfill");
                }
                return null;
            }

            Backfill bf = new Backfill(null, null, null, null);
            bf.setBackfillEnabled(rs.getBoolean(1));
            bf.setMaxInstances(rs.getInt(2));
            bf.setDiskImage(rs.getString(3));
            bf.setSiteCapacity(rs.getInt(4));
            bf.setRepoUser(rs.getString(5));
            return bf;

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public synchronized void setBackfill(Backfill bf) throws WorkspaceDatabaseException {
        if (bf == null) {
            throw new IllegalArgumentException("backfill may not be null");
        }

        if (this.dbTrace) {
            logger.trace("setBackfill()");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();

            // Need to determine whether to use insert or update
            Backfill previous = this.getStoredBackfill();
            if (previous == null) {
                pstmt = c.prepareStatement(SQL_INSERT_BACKFILL);
            } else {
                pstmt = c.prepareStatement(SQL_UPDATE_BACKFILL);
            }

            pstmt.setInt(1, bf.isBackfillEnabled() ? 1 : 0);
            pstmt.setInt(2, bf.getMaxInstances());
            pstmt.setString(3, bf.getDiskImage());
            pstmt.setInt(4, bf.getSiteCapacity());
            pstmt.setString(5, bf.getRepoUser());
            pstmt.setInt(6, bf.getInstanceMem());

            final int updated = pstmt.executeUpdate();
            if (this.dbTrace) {
                logger.trace("Updated/inserted backfill");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    /**
     * Retrieves idempotency reservation
     *
     * @param creatorId   initiating client user
     * @param clientToken client-provided idempotency token
     * @return stored reservation, or null of not found
     * @throws WorkspaceDatabaseException
     *          DB error
     */
    public IdempotentReservation getIdempotentReservation(String creatorId, String clientToken)
            throws WorkspaceDatabaseException {

        if (creatorId == null) {
            throw new IllegalArgumentException("creatorId may not be null");
        }
        if (clientToken == null) {
            throw new IllegalArgumentException("clientToken may not be null");
        }

        if (this.dbTrace) {
            logger.trace("getIdempotentReservation()");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();

            pstmt = c.prepareStatement(SQL_SELECT_IDEMPOTENT_CREATION);

            pstmt.setString(1, creatorId);
            pstmt.setString(2, clientToken);

            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                if (lager.traceLog) {
                    logger.debug("no existing idempotency reservation");
                }
                return null;
            }

            ArrayList<IdempotentInstance> instances = new ArrayList<IdempotentInstance>();
            // rs was next'd above already
            final String groupId = rs.getString(2);
            do {
                final int vmId = rs.getInt(1);
                final String name = rs.getString(3);
                final int launchIndex = rs.getInt(4);

                instances.add(new IdempotentInstanceImpl(vmId, name, launchIndex));
            } while (rs.next());

            return new IdempotentReservationImpl(creatorId, clientToken, groupId, instances);

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }

                if (rs != null) {
                    rs.close();
                }

                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    /**
     * Stores idempotency reservation
     *
     * @param reservation the reservation to store
     * @throws WorkspaceDatabaseException
     *          DB error
     */
    public void addIdempotentReservation(IdempotentReservation reservation) throws WorkspaceDatabaseException {
        if (this.dbTrace) {
            logger.trace("addIdempotentReservation()");
        }

        Connection c = null;
        PreparedStatement[] pstmts = null;
        try {
            c = getConnection();
            c.setAutoCommit(false);

            pstmts = IdempotencyPersistenceUtil.getInsertReservation(reservation, c);

            for (PreparedStatement pstmt : pstmts) {
                pstmt.executeUpdate();
            }
            c.commit();

            if (this.dbTrace) {
                logger.trace("Inserted idempotency reservation");
            }

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmts != null) {
                    for (PreparedStatement pstmt : pstmts) {
                        pstmt.close();
                    }
                }
                if (c != null) {
                    c.setAutoCommit(true);
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    /**
     * Removes existing idempotency reservation
     *
     * @param creatorId   initiating client user
     * @param clientToken client-provided idempotency token
     * @throws WorkspaceDatabaseException
     *          DB error
     */
    public void removeIdempotentReservation(String creatorId, String clientToken)
            throws WorkspaceDatabaseException {

        if (creatorId == null) {
            throw new IllegalArgumentException("creatorId may not be null");
        }
        if (clientToken == null) {
            throw new IllegalArgumentException("clientToken may not be null");
        }

        if (this.dbTrace) {
            logger.trace("removeIdempotentReservation()");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        try {
            c = getConnection();
            pstmt = c.prepareStatement(SQL_DELETE_IDEMPOTENT_CREATION);
            pstmt.setString(1, creatorId);
            pstmt.setString(2, clientToken);

            pstmt.executeUpdate();

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public void addAsyncRequest(AsyncRequest asyncRequest) throws WorkspaceDatabaseException {

        if (asyncRequest == null) {
            throw new IllegalArgumentException("asyncRequest may not be null");
        }

        Connection c = null;
        PreparedStatement pstmt = null;
        PreparedStatement[] pstmts = null;
        try {
            c = getConnection();

            AsyncRequest oldAsyncRequest = getAsyncRequest(asyncRequest.getId());

            if (oldAsyncRequest != null) {
                logger.debug("Updating old request " + oldAsyncRequest.getId());
                // We will later need to persist modified versions of our persisted VMs,
                // so we need to remove all of our earlier copies
                pstmts = AsyncRequestMapPersistenceUtil.getRemoveAsyncBindings(oldAsyncRequest, c);
                for (PreparedStatement p : pstmts) {

                    p.executeUpdate();
                }
                AsyncRequestMapPersistenceUtil.removeAllocatedVMs(oldAsyncRequest, c);
                AsyncRequestMapPersistenceUtil.removeFinishedVMs(oldAsyncRequest, c);
                AsyncRequestMapPersistenceUtil.removeToBePreempted(oldAsyncRequest, c);

                pstmt = AsyncRequestMapPersistenceUtil.getUpdateAsyncRequest(asyncRequest, this.repr, c);
                pstmt.executeUpdate();
                c.commit();
            } else {
                logger.debug("Persisting request: " + asyncRequest.getId());
                pstmt = AsyncRequestMapPersistenceUtil.getInsertAsyncRequest(asyncRequest, this.repr, c);
                pstmt.executeUpdate();
            }

            AsyncRequestMapPersistenceUtil.putAllocatedVMs(asyncRequest, c);
            AsyncRequestMapPersistenceUtil.putFinishedVMs(asyncRequest, c);
            AsyncRequestMapPersistenceUtil.putToBePreempted(asyncRequest, c);
            AsyncRequestMapPersistenceUtil.putAsyncRequestBindings(asyncRequest, c);
            c.commit();

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } catch (ManageException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (pstmts != null) {
                    for (PreparedStatement p : pstmts) {
                        p.close();
                    }
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }
    }

    public AsyncRequest getAsyncRequest(String id) throws WorkspaceDatabaseException {

        if (id == null) {
            throw new IllegalArgumentException("id may not be null");
        }

        AsyncRequest asyncRequest = null;

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();
            pstmt = AsyncRequestMapPersistenceUtil.getAsyncRequest(id, c);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                logger.debug("No Asyncrequest with ID " + id);
                return null;
            }

            asyncRequest = AsyncRequestMapPersistenceUtil.rsToAsyncRequest(rs, this.repr, c);
            VirtualMachine[] bindings = AsyncRequestMapPersistenceUtil.getAsyncVMs(asyncRequest.getId(), c);
            asyncRequest.setBindings(bindings);
            AsyncRequestMapPersistenceUtil.addAllocatedVMs(asyncRequest, c);
            AsyncRequestMapPersistenceUtil.addFinishedVMs(asyncRequest, c);
            AsyncRequestMapPersistenceUtil.addToBePreempted(asyncRequest, c);

        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } catch (CannotTranslateException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }

        return asyncRequest;
    }

    public ArrayList<AsyncRequest> getAllAsyncRequests() throws WorkspaceDatabaseException {

        ArrayList<AsyncRequest> asyncRequests = new ArrayList<AsyncRequest>();

        Connection c = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            c = getConnection();
            pstmt = AsyncRequestMapPersistenceUtil.getAllAsyncRequests(c);
            rs = pstmt.executeQuery();

            if (rs == null || !rs.next()) {
                return asyncRequests;
            }

            do {
                String id = rs.getString("id");
                AsyncRequest asyncRequest = this.getAsyncRequest(id);
                asyncRequests.add(asyncRequest);
            } while (rs.next());
        } catch (SQLException e) {
            logger.error("", e);
            throw new WorkspaceDatabaseException(e);
        } finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
                if (c != null) {
                    returnConnection(c);
                }
            } catch (SQLException sql) {
                logger.error("SQLException in finally cleanup", sql);
            }
        }

        return asyncRequests;
    }
}