org.apereo.portal.concurrency.locking.RDBMEntityLockStore.java Source code

Java tutorial

Introduction

Here is the source code for org.apereo.portal.concurrency.locking.RDBMEntityLockStore.java

Source

/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you 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 the following location:
 *
 *   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.apereo.portal.concurrency.locking;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.jdbc.RDBMServices;
import org.apereo.portal.concurrency.IEntityLock;
import org.apereo.portal.concurrency.LockingException;
import org.apereo.portal.spring.locator.EntityTypesLocator;

/**
 * RDBMS-based store for <code>IEntityLocks</code>.
 * @author Dan Ellentuck
 */
public class RDBMEntityLockStore implements IEntityLockStore {
    private static final Log log = LogFactory.getLog(RDBMEntityLockStore.class);
    private static IEntityLockStore singleton;

    // Constants for the LOCK table:
    private static String LOCK_TABLE = "UP_ENTITY_LOCK";
    private static String ENTITY_TYPE_COLUMN = "ENTITY_TYPE_ID";
    private static String ENTITY_KEY_COLUMN = "ENTITY_KEY";
    private static String EXPIRATION_TIME_COLUMN = "EXPIRATION_TIME";
    private static String LOCK_OWNER_COLUMN = "LOCK_OWNER";
    private static String LOCK_TYPE_COLUMN = "LOCK_TYPE";
    private static String EQ = " = ";
    private static String GT = " > ";
    private static String LT = " < ";
    private static String QUOTE = "'";

    private static String allLockColumns;
    private static String addSql;
    private static String deleteLockSql;
    private static String updateSql;

    // Prior to jdk 1.4, java.sql.Timestamp.getTime() truncated milliseconds.
    private static boolean timestampHasMillis;
    static {
        Date testDate = new Date();
        Timestamp testTimestamp = new Timestamp(testDate.getTime());
        timestampHasMillis = (testDate.getTime() == testTimestamp.getTime());
    }

    /**
     * RDBMEntityGroupStore constructor.
     */
    public RDBMEntityLockStore() throws LockingException {
        super();
        initialize();
    }

    /**
     * Adds the lock to the underlying store.
     * @param lock
     */
    public void add(IEntityLock lock) throws LockingException {
        Connection conn = null;
        try {
            conn = RDBMServices.getConnection();
            primDeleteExpired(new Date(), lock.getEntityType(), lock.getEntityKey(), conn);
            primAdd(lock, conn);
        }

        catch (SQLException sqle) {
            throw new LockingException("Problem creating " + lock, sqle);
        }

        finally {
            RDBMServices.releaseConnection(conn);
        }
    }

    /**
     * If this IEntityLock exists, delete it.
     * @param lock
     */
    public void delete(IEntityLock lock) throws LockingException {
        Connection conn = null;
        try {
            conn = RDBMServices.getConnection();
            primDelete(lock, conn);
        }

        catch (SQLException sqle) {
            throw new LockingException("Problem deleting " + lock, sqle);
        } finally {
            RDBMServices.releaseConnection(conn);
        }
    }

    /**
     * Delete all IEntityLocks from the underlying store.
     */
    public void deleteAll() throws LockingException {
        Connection conn = null;
        Statement stmnt = null;
        try {
            String sql = "DELETE FROM " + LOCK_TABLE;
            if (log.isDebugEnabled())
                log.debug("RDBMEntityLockStore.deleteAll(): " + sql);

            conn = RDBMServices.getConnection();
            try {
                stmnt = conn.createStatement();
                int rc = stmnt.executeUpdate(sql);
                if (log.isDebugEnabled()) {
                    String msg = "Deleted " + rc + " locks.";
                    log.debug("RDBMEntityLockStore.deleteAll(): " + msg);
                }
            } finally {
                if (stmnt != null)
                    stmnt.close();
            }
        } catch (SQLException sqle) {
            throw new LockingException("Problem deleting locks", sqle);
        }

        finally {
            RDBMServices.releaseConnection(conn);
        }
    }

    /**
     * Delete all expired IEntityLocks from the underlying store.
     * @param expiration
     */
    public void deleteExpired(Date expiration) throws LockingException {
        deleteExpired(expiration, null, null);
    }

    /**
     * Delete IEntityLocks from the underlying store that have expired as of
     * <code>expiration</code>.  Params <code>entityType</code> and
     * <code>entityKey</code> are optional.
     *
     * @param expiration java.util.Date
     * @param entityType Class
     * @param entityKey String
     */
    public void deleteExpired(Date expiration, Class entityType, String entityKey) throws LockingException {
        Connection conn = null;
        try {
            conn = RDBMServices.getConnection();
            primDeleteExpired(expiration, entityType, entityKey, conn);
        }

        catch (SQLException sqle) {
            throw new LockingException("Problem deleting expired locks", sqle);
        } finally {
            RDBMServices.releaseConnection(conn);
        }
    }

    /**
     * Delete all expired IEntityLocks from the underlying store.
     * @param lock IEntityLock
     */
    public void deleteExpired(IEntityLock lock) throws LockingException {
        deleteExpired(new Date(), lock.getEntityType(), lock.getEntityKey());
    }

    /**
     * Retrieve IEntityLocks from the underlying store.  Any or all of the parameters
     * may be null.
     * @param entityType Class
     * @param entityKey String
     * @param lockType Integer - so we can accept a null value.
     * @param expiration Date
     * @param lockOwner String
     * @exception LockingException - wraps an Exception specific to the store.
     */
    public IEntityLock[] find(Class entityType, String entityKey, Integer lockType, Date expiration,
            String lockOwner) throws LockingException {
        return select(entityType, entityKey, lockType, expiration, lockOwner);
    }

    /**
     * Retrieve IEntityLocks from the underlying store.  Expiration must not be null.
     * @param expiration Date
     * @param entityType Class
     * @param entityKey String
     * @param lockType Integer - so we can accept a null value.
     * @param lockOwner String
     * @exception LockingException - wraps an Exception specific to the store.
     */
    public IEntityLock[] findUnexpired(Date expiration, Class entityType, String entityKey, Integer lockType,
            String lockOwner) throws LockingException {
        Timestamp ts = new Timestamp(expiration.getTime());
        return selectUnexpired(ts, entityType, entityKey, lockType, lockOwner);
    }

    /**
     * SQL for inserting a row into the lock table.
     */
    private static String getAddSql() {
        if (addSql == null) {
            addSql = "INSERT INTO " + LOCK_TABLE + "(" + getAllLockColumns() + ") VALUES (?, ?, ?, ?, ?)";
        }
        return addSql;
    }

    /**
     * @return java.lang.String
     */
    private static java.lang.String getAllLockColumns() {
        if (allLockColumns == null) {
            StringBuffer buff = new StringBuffer(100);
            buff.append(ENTITY_TYPE_COLUMN);
            buff.append(", ");
            buff.append(ENTITY_KEY_COLUMN);
            buff.append(", ");
            buff.append(LOCK_TYPE_COLUMN);
            buff.append(", ");
            buff.append(EXPIRATION_TIME_COLUMN);
            buff.append(", ");
            buff.append(LOCK_OWNER_COLUMN);

            allLockColumns = buff.toString();
        }
        return allLockColumns;
    }

    /**
     * SQL for deleting a row on the lock table.
     */
    private static String getDeleteLockSql() {
        if (deleteLockSql == null) {
            deleteLockSql = "DELETE FROM " + LOCK_TABLE + " WHERE " + ENTITY_TYPE_COLUMN + EQ + "?" + " AND "
                    + ENTITY_KEY_COLUMN + EQ + "?" + " AND " + EXPIRATION_TIME_COLUMN + EQ + "?" + " AND "
                    + LOCK_TYPE_COLUMN + EQ + "?" + " AND " + LOCK_OWNER_COLUMN + EQ + "?";
        }
        return deleteLockSql;
    }

    /**
     * @return java.lang.String
     */
    private static java.lang.String getSelectSql() {
        return ("SELECT " + getAllLockColumns() + " FROM " + LOCK_TABLE);
    }

    /**
     * SQL for updating a row on the lock table.
     */
    private static String getUpdateSql() {
        if (updateSql == null) {
            updateSql = "UPDATE " + LOCK_TABLE + " SET " + EXPIRATION_TIME_COLUMN + EQ + "?, " + LOCK_TYPE_COLUMN
                    + EQ + "?" + " WHERE " + ENTITY_TYPE_COLUMN + EQ + "?" + " AND " + ENTITY_KEY_COLUMN + EQ + "?"
                    + " AND " + LOCK_OWNER_COLUMN + EQ + "?" + " AND " + EXPIRATION_TIME_COLUMN + EQ + "?" + " AND "
                    + LOCK_TYPE_COLUMN + EQ + "?";
        }
        return updateSql;
    }

    /**
     * Cleanup the store by deleting locks expired an hour ago.
     */
    private void initialize() throws LockingException {
        Date expiration = new Date(System.currentTimeMillis() - (60 * 60 * 1000));
        deleteExpired(expiration, null, null);
    }

    /**
     * Extract values from ResultSet and create a new lock.
     * @return org.apereo.portal.groups.IEntityLock
     * @param rs java.sql.ResultSet
     */
    private IEntityLock instanceFromResultSet(java.sql.ResultSet rs) throws SQLException, LockingException {
        Integer entityTypeID = new Integer(rs.getInt(1));
        Class entityType = EntityTypesLocator.getEntityTypes().getEntityTypeFromID(entityTypeID);
        String key = rs.getString(2);
        int lockType = rs.getInt(3);
        Timestamp ts = rs.getTimestamp(4);
        String lockOwner = rs.getString(5);

        return newInstance(entityType, key, lockType, ts, lockOwner);
    }

    /**
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     */
    private IEntityLock newInstance(Class entityType, String entityKey, int lockType, Date expirationTime,
            String lockOwner) throws LockingException {
        return new EntityLockImpl(entityType, entityKey, lockType, expirationTime, lockOwner);
    }

    /**
     * Add the lock to the underlying store.
     * @param lock org.apereo.portal.concurrency.locking.IEntityLock
     * @param conn java.sql.Connection
     */
    private void primAdd(IEntityLock lock, Connection conn) throws SQLException, LockingException {
        Integer typeID = EntityTypesLocator.getEntityTypes().getEntityIDFromType(lock.getEntityType());
        String key = lock.getEntityKey();
        int lockType = lock.getLockType();
        Timestamp ts = new Timestamp(lock.getExpirationTime().getTime());
        String owner = lock.getLockOwner();

        try {
            PreparedStatement ps = conn.prepareStatement(getAddSql());
            try {
                ps.setInt(1, typeID.intValue()); // entity type
                ps.setString(2, key); // entity key
                ps.setInt(3, lockType); // lock type
                ps.setTimestamp(4, ts); // lock expiration
                ps.setString(5, owner); // lock owner

                if (log.isDebugEnabled())
                    log.debug("RDBMEntityLockStore.primAdd(): " + ps);

                int rc = ps.executeUpdate();
                if (rc != 1) {
                    String errString = "Problem adding " + lock;
                    log.error(errString);
                    throw new LockingException(errString);
                }
            } finally {
                if (ps != null)
                    ps.close();
            }
        } catch (java.sql.SQLException sqle) {
            log.error(sqle, sqle);
            throw sqle;
        }
    }

    /**
     * Delete the IEntityLock from the underlying store.
     * @param lock
     * @param conn the database connection
     */
    private void primDelete(IEntityLock lock, Connection conn) throws LockingException, SQLException {
        Integer typeID = EntityTypesLocator.getEntityTypes().getEntityIDFromType(lock.getEntityType());
        String key = lock.getEntityKey();
        int lockType = lock.getLockType();
        Timestamp ts = new Timestamp(lock.getExpirationTime().getTime());
        String owner = lock.getLockOwner();

        try {
            PreparedStatement ps = conn.prepareStatement(getDeleteLockSql());
            try {
                ps.setInt(1, typeID.intValue()); // entity type
                ps.setString(2, key); // entity key
                ps.setTimestamp(3, ts); // lock expiration
                ps.setInt(4, lockType); // lock type
                ps.setString(5, owner); // lock owner

                if (log.isDebugEnabled())
                    log.debug("RDBMEntityLockStore.primDelete(): " + ps);

                int rc = ps.executeUpdate();
                if (log.isDebugEnabled())
                    log.debug("RDBMEntityLockStore.primDelete(): deleted " + rc + " lock(s).");
            } finally {
                if (ps != null)
                    ps.close();
            }
        } catch (java.sql.SQLException sqle) {
            log.error(sqle, sqle);
            throw sqle;
        }
    }

    /**
     * Delete IEntityLocks from the underlying store that have expired as of
     * <code>expiration</code>.  Params <code>entityType</code> and
     * <code>entityKey</code> are optional.
     *
     * @param expiration java.util.Date
     * @param entityType Class
     * @param entityKey String
     * @param conn Connection
     */
    private void primDeleteExpired(Date expiration, Class entityType, String entityKey, Connection conn)
            throws LockingException, SQLException {
        Statement stmnt = null;
        Timestamp ts = new Timestamp(expiration.getTime());

        StringBuffer buff = new StringBuffer(100);
        buff.append("DELETE FROM " + LOCK_TABLE + " WHERE " + EXPIRATION_TIME_COLUMN + LT);
        buff.append(printTimestamp(ts));
        if (entityType != null) {
            Integer typeID = EntityTypesLocator.getEntityTypes().getEntityIDFromType(entityType);
            buff.append(" AND " + ENTITY_TYPE_COLUMN + EQ + typeID);
        }
        if (entityKey != null) {
            buff.append(" AND " + ENTITY_KEY_COLUMN + EQ + sqlQuote(entityKey));
        }

        String sql = buff.toString();

        if (log.isDebugEnabled())
            log.debug("RDBMEntityLockStore.deleteExpired(): " + sql);

        try {
            stmnt = conn.createStatement();
            int rc = stmnt.executeUpdate(sql);
            if (log.isDebugEnabled()) {
                String msg = "Deleted " + rc + " expired locks.";
                log.debug("RDBMEntityLockStore.deleteExpired(): " + msg);
            }

        }

        catch (SQLException sqle) {
            throw new LockingException("Problem deleting expired locks", sqle);
        }

        finally {
            if (stmnt != null)
                stmnt.close();
        }
    }

    /**
     * Retrieve IEntityLocks from the underlying store.
     * @param sql String - the sql string used to select the entity lock rows.
     * @exception LockingException - wraps an Exception specific to the store.
     */
    private IEntityLock[] primSelect(String sql) throws LockingException {
        Connection conn = null;
        Statement stmnt = null;
        ResultSet rs = null;
        List locks = new ArrayList();

        if (log.isDebugEnabled())
            log.debug("RDBMEntityLockStore.primSelect(): " + sql);

        try {
            conn = RDBMServices.getConnection();
            stmnt = conn.createStatement();
            try {
                rs = stmnt.executeQuery(sql);
                try {
                    while (rs.next()) {
                        locks.add(instanceFromResultSet(rs));
                    }
                } finally {
                    rs.close();
                }
            } finally {
                stmnt.close();
            }
        } catch (SQLException sqle) {
            log.error(sqle, sqle);
            throw new LockingException("Problem retrieving EntityLocks", sqle);
        } finally {
            RDBMServices.releaseConnection(conn);
        }

        return ((IEntityLock[]) locks.toArray(new IEntityLock[locks.size()]));
    }

    /**
     * Updates the lock's <code>expiration</code> and <code>lockType</code> in the
     * underlying store.  The SQL is over-qualified to make sure the row has not been
     * updated since the lock was last checked.
     * @param lock
     * @param newExpiration java.util.Date
     * @param newType Integer
     * @param conn Connection
     */
    private void primUpdate(IEntityLock lock, Date newExpiration, Integer newType, Connection conn)
            throws SQLException, LockingException {
        Integer typeID = EntityTypesLocator.getEntityTypes().getEntityIDFromType(lock.getEntityType());
        String key = lock.getEntityKey();
        int oldLockType = lock.getLockType();
        int newLockType = (newType == null) ? oldLockType : newType.intValue();
        java.sql.Timestamp oldTs = new java.sql.Timestamp(lock.getExpirationTime().getTime());
        java.sql.Timestamp newTs = new java.sql.Timestamp(newExpiration.getTime());
        String owner = lock.getLockOwner();

        try {
            PreparedStatement ps = conn.prepareStatement(getUpdateSql());
            try {
                ps.setTimestamp(1, newTs); // new expiration
                ps.setInt(2, newLockType); // new lock type
                ps.setInt(3, typeID.intValue()); // entity type
                ps.setString(4, key); // entity key
                ps.setString(5, owner); // lock owner
                ps.setTimestamp(6, oldTs); // old expiration
                ps.setInt(7, oldLockType); // old lock type;

                if (log.isDebugEnabled())
                    log.debug("RDBMEntityLockStore.primUpdate(): " + ps);

                int rc = ps.executeUpdate();
                if (rc != 1) {
                    String errString = "Problem updating " + lock;
                    log.error(errString);
                    throw new LockingException(errString);
                }
            } finally {
                if (ps != null)
                    ps.close();
            }
        } catch (java.sql.SQLException sqle) {
            log.error(sqle, sqle);
            throw sqle;
        }
    }

    /**
     * Retrieve IEntityLocks from the underlying store.  Any or all of the parameters
     * may be null.
     * @param entityType Class
     * @param entityKey String
     * @param lockType Integer - so we can accept a null value.
     * @param expiration Date
     * @param lockOwner String
     * @exception LockingException - wraps an Exception specific to the store.
     */
    private IEntityLock[] select(Class entityType, String entityKey, Integer lockType, Date expiration,
            String lockOwner) throws LockingException {
        StringBuffer sqlQuery = new StringBuffer(getSelectSql() + " WHERE 1 = 1");

        if (entityType != null) {
            Integer typeID = EntityTypesLocator.getEntityTypes().getEntityIDFromType(entityType);
            sqlQuery.append(" AND " + ENTITY_TYPE_COLUMN + EQ + typeID);
        }

        if (entityKey != null) {
            sqlQuery.append(" AND " + ENTITY_KEY_COLUMN + EQ + sqlQuote(entityKey));
        }

        if (lockType != null) {
            sqlQuery.append(" AND " + LOCK_TYPE_COLUMN + EQ + lockType);
        }

        if (expiration != null) {
            Timestamp ts = new Timestamp(expiration.getTime());
            sqlQuery.append(" AND " + EXPIRATION_TIME_COLUMN + EQ + printTimestamp(ts));
        }

        if (lockOwner != null) {
            sqlQuery.append(" AND " + LOCK_OWNER_COLUMN + EQ + sqlQuote(lockOwner));
        }

        return primSelect(sqlQuery.toString());
    }

    /**
     * Retrieve IEntityLocks from the underlying store.  Expiration must not be null.
     * @param entityType Class
     * @param entityKey String
     * @param lockType Integer - so we can accept a null value.
     * @param lockOwner String
     * @exception LockingException - wraps an Exception specific to the store.
     */
    private IEntityLock[] selectUnexpired(Timestamp ts, Class entityType, String entityKey, Integer lockType,
            String lockOwner) throws LockingException {
        StringBuffer sqlQuery = new StringBuffer(getSelectSql());

        sqlQuery.append(" WHERE " + EXPIRATION_TIME_COLUMN + GT + printTimestamp(ts));

        if (entityType != null) {
            Integer typeID = EntityTypesLocator.getEntityTypes().getEntityIDFromType(entityType);
            sqlQuery.append(" AND " + ENTITY_TYPE_COLUMN + EQ + typeID);
        }

        if (entityKey != null) {
            sqlQuery.append(" AND " + ENTITY_KEY_COLUMN + EQ + sqlQuote(entityKey));
        }

        if (lockType != null) {
            sqlQuery.append(" AND " + LOCK_TYPE_COLUMN + EQ + lockType);
        }

        if (lockOwner != null) {
            sqlQuery.append(" AND " + LOCK_OWNER_COLUMN + EQ + sqlQuote(lockOwner));
        }

        return primSelect(sqlQuery.toString());
    }

    /**
     * @return org.apereo.portal.concurrency.locking.RDBMEntityLockStore
     */
    public static synchronized IEntityLockStore singleton() throws LockingException {
        if (singleton == null) {
            singleton = new RDBMEntityLockStore();
        }
        return singleton;
    }

    /**
     * @return java.lang.String
     */
    private static java.lang.String sqlQuote(Object o) {
        return QUOTE + o + QUOTE;
    }

    /**
     * @param lock org.apereo.portal.groups.IEntityLock
     * @param newExpiration java.util.Date
     */
    public void update(IEntityLock lock, java.util.Date newExpiration) throws LockingException {
        update(lock, newExpiration, null);
    }

    /**
     * Updates the lock's <code>expiration</code> and <code>lockType</code> in the
     * underlying store.  Param <code>lockType</code> may be null.
     * @param lock
     * @param newExpiration java.util.Date
     * @param newLockType Integer
     */
    public void update(IEntityLock lock, Date newExpiration, Integer newLockType) throws LockingException {
        Connection conn = null;
        try {
            conn = RDBMServices.getConnection();
            if (newLockType != null) {
                primDeleteExpired(new Date(), lock.getEntityType(), lock.getEntityKey(), conn);
            }
            primUpdate(lock, newExpiration, newLockType, conn);
        }

        catch (SQLException sqle) {
            throw new LockingException("Problem updating " + lock, sqle);
        } finally {
            RDBMServices.releaseConnection(conn);
        }
    }

    /**
     * @return long
     */
    private static long getTimestampMillis(Timestamp ts) {
        if (timestampHasMillis) {
            return ts.getTime();
        } else {
            return (ts.getTime() + ts.getNanos() / 1000000);
        }
    }

    /**
     * @return java.lang.String
     */
    private static java.lang.String printTimestamp(Timestamp ts) {
        return RDBMServices.getDbMetaData().sqlTimeStamp(getTimestampMillis(ts));
    }
}