/** * */ package org.jboss.web.tomcat.service.session.persistent; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.jboss.ha.framework.server.SimpleCachableMarshalledValue; import org.jboss.logging.Logger; import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata; import org.jboss.web.tomcat.service.session.distributedcache.spi.IncomingDistributableSessionData; import org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingSessionGranularitySessionData; import org.jboss.web.tomcat.service.session.distributedcache.spi.SessionSerializationFactory; /** * Abstract superclass for {@link PersistentStore} implementations that store in a * relational database. * * @author Brian Stansberry * * @version $Revision: $ */ public abstract class RDBMSStoreBase implements PersistentStore { private static final Logger LOG = Logger.getLogger(RDBMSStoreBase.class); public static final String DEFAULT_TABLE = "httpsessions"; public static final String DEFAULT_APP_COL = "app"; public static final String DEFAULT_ID_COL = "id"; public static final String DEFAULT_FULLID_COL = "fullid"; public static final String DEFAULT_ATTRIBUTE_COL = "attributes"; public static final String DEFAULT_METADATA_COL = "metadata"; public static final String DEFAULT_ISNEW_COL = "isnew"; public static final String DEFAULT_ISVALID_COL = "valid"; public static final String DEFAULT_CREATION_TIME_COL = "creationtime"; public static final String DEFAULT_LAST_ACCESSED_COL = "lastaccess"; public static final String DEFAULT_MAX_INACTIVE_COL = "maxinactive"; public static final String DEFAULT_VERSION_COL = "version"; public static final int DEFAULT_CLEANUP_INTERVAL = 4 * 60 * 60; // --------------------------------------------------------- Instance Fields /** Any inject logger */ private Logger logger = null; /** * Has this component been started yet? */ private boolean started = false; /** * The string manager for this package. */ // protected final StringManager sm = StringManager.getManager(Constants.Package); /** * Context name associated with this Store */ private String name = null; /** * How often to execute the processExpires cleanup */ private int cleanupInterval = DEFAULT_CLEANUP_INTERVAL; /** When we last executed the processExpires cleanup */ private long lastCleanup = 0; private int maxUnreplicatedInterval = -1; /** * The connection username to use when trying to connect to the database. */ private String connectionName = null; /** * The connection URL to use when trying to connect to the database. */ private String connectionPassword = null; /** * Table to use. */ private String sessionTable = DEFAULT_TABLE; /** * Column to use for /Engine/Host/Context name */ private String sessionAppCol = DEFAULT_APP_COL; /** * Id column to use. */ private String sessionIdCol = DEFAULT_ID_COL; /** * Full Id (e.g. including jvmRoute) column to use. */ private String sessionFullIdCol = DEFAULT_FULLID_COL; /** * Creation time column to use */ private String sessionCreationTimeCol = DEFAULT_CREATION_TIME_COL; /** * Max Inactive column to use. */ private String sessionMaxInactiveCol = DEFAULT_MAX_INACTIVE_COL; /** * Version column to use. */ private String sessionVersionCol = DEFAULT_VERSION_COL; /** * Last Accessed column to use. */ private String sessionLastAccessedCol = DEFAULT_LAST_ACCESSED_COL; /** * Is New column to use */ private String sessionNewCol = DEFAULT_ISNEW_COL; /** * Is Valid column to use. */ private String sessionValidCol = DEFAULT_ISVALID_COL; /** * Column to use for misc metadata. */ private String sessionMetadataCol = DEFAULT_METADATA_COL; /** * Attribute column to use. */ private String sessionAttributeCol = DEFAULT_ATTRIBUTE_COL; private String clearSql; private String sizeSql; private String insertSql; private String fullUpdateSql; private String keysSql; private String fullLoadSql; private String partialLoadSql; private String reinsertSql; private String removeSql; private String versionSql; private String timestampSql; private final Map<Connection, Set<Statement>> statementsByConnection = new ConcurrentHashMap<Connection, Set<Statement>>(); private String simpleUpdateSql; private String attributeUpdateSql; private String metadataUpdateSql; private String cleanupSql; private final byte[] emptyAttributes; // -------------------------------------------------------- Constructors protected RDBMSStoreBase() { try { @SuppressWarnings("unchecked") Object empty = new SimpleCachableMarshalledValue(new HashMap()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(empty); oos.close(); emptyAttributes = baos.toByteArray(); } catch (IOException e) { throw new RuntimeException("Cannot serialize simple HashMap"); } } // ------------------------------------------------------------- Properties /** * Return the name for this Store, used for logging. */ public abstract String getStoreName(); /** * Return the info for this Store. */ public abstract String getInfo(); /** * Return the name for this instance (built from container name) */ public String getName() { if (name == null) { throw new IllegalStateException("Must configure a name for PersistentStore"); } return name; } public void setName(String name) { this.name = name; } /** * Return the username to use to connect to the database. * */ public String getConnectionName() { return (this.connectionName); } /** * Set the username to use to connect to the database. * * @param connectionName Username */ public void setConnectionName(String connectionName) { this.connectionName = connectionName; } /** * Return the password to use to connect to the database. * */ public String getConnectionPassword() { return (this.connectionPassword); } /** * Set the password to use to connect to the database. * * @param connectionPassword User password */ public void setConnectionPassword(String connectionPassword) { this.connectionPassword = connectionPassword; } /** * Set the table for this Store. * * @param sessionTable The new table */ public void setSessionTable(String sessionTable) { this.sessionTable = sessionTable; } /** * Return the table for this Store. */ public String getSessionTable() { return (this.sessionTable); } /** * Set the App column for the table. * * @param sessionAppCol the column name */ public void setSessionAppCol(String sessionAppCol) { this.sessionAppCol = sessionAppCol; } /** * Return the web application name column for the table. */ public String getSessionAppCol() { return (this.sessionAppCol); } /** * Return the name of the session id column for the table. This is where the * core, immutable part of the session id is stored. */ public String getSessionIdCol() { return (this.sessionIdCol); } /** * Set the name of the session id column for the table. This is where the * core, immutable part of the session id is stored. * * @param sessionIdCol the column name */ public void setSessionIdCol(String sessionIdCol) { this.sessionIdCol = sessionIdCol; } /** * Returns the name of the full session id column for the table. This is where * the full id including any mutable element (e.g. a jvmRoute) that is added * to the {@link #getSessionIdCol() core session id} is stored. */ public String getSessionFullIdCol() { return (this.sessionFullIdCol); } /** * Set the name of the full session id column for the table. This is where * the full id including any mutable element (e.g. a jvmRoute) that is added * to the {@link #getSessionIdCol() core session id} is stored. * * @param sessionFullIdCol the column name */ public void setSessionFullIdCol(String sessionFullIdCol) { this.sessionFullIdCol = sessionFullIdCol; } /** * Gets the name of the column where the session creation time is stored. * * @return the column name */ public String getSessionCreationTimeCol() { return (this.sessionCreationTimeCol); } /** * Sets the name of the column where the session creation time is stored. * * param sessionCreationTimeCol the column name */ public void setSessionCreationTimeCol(String sessionCreationTimeCol) { this.sessionCreationTimeCol = sessionCreationTimeCol; } /** * Return the Max Inactive column */ public String getSessionMaxInactiveCol() { return (this.sessionMaxInactiveCol); } /** * Set the Max Inactive column for the table * * @param sessionMaxInactiveCol The column name */ public void setSessionMaxInactiveCol(String sessionMaxInactiveCol) { this.sessionMaxInactiveCol = sessionMaxInactiveCol; } /** * Gets the name of "session is new" marker column * * @return the column name */ public String getSessionNewCol() { return (this.sessionNewCol); } /** * Sets the name of "session is new" marker column * * @param sessionNewCol the column name */ public void setSessionNewCol(String sessionNewCol) { this.sessionNewCol = sessionNewCol; } /** * Return the name of the session version column */ public String getSessionVersionCol() { return (this.sessionVersionCol); } /** * Set the name of the session version column for the table * * @param sessionVersionCol The column name */ public void setSessionVersionCol(String sessionVersionCol) { this.sessionVersionCol = sessionVersionCol; } /** * Return the name of the session last access timestamp column */ public String getSessionLastAccessedCol() { return (this.sessionLastAccessedCol); } /** * Set the name of the session last access timestamp column for the table * * @param sessionLastAccessedCol The column name */ public void setSessionLastAccessedCol(String sessionLastAccessedCol) { this.sessionLastAccessedCol = sessionLastAccessedCol; } /** * Return the Iname of the session validity marker column */ public String getSessionValidCol() { return (this.sessionValidCol); } /** * Set the name of the session validity marker column for the table * * @param sessionValidCol The column name */ public void setSessionValidCol(String sessionValidCol) { this.sessionValidCol = sessionValidCol; } /** * Return the name of the misc misc metadata storage column */ public String getSessionMetadataCol() { return (this.sessionMetadataCol); } /** * Set the name of the misc metadata storage column for the table * * @param sessionValidCol The column name */ public void setSessionMetadataCol(String sessionMetadataCol) { this.sessionMetadataCol = sessionMetadataCol; } /** * Return the attribute storage column for the table */ public String getSessionAttributeCol() { return (this.sessionAttributeCol); } /** * Set the attribute storage column for the table * * @param sessionAttributeCol the column name */ public void setSessionAttributeCol(String sessionAttributeCol) { this.sessionAttributeCol = sessionAttributeCol; } public int getCleanupInterval() { return cleanupInterval; } public void setCleanupInterval(int cleanupInterval) { this.cleanupInterval = cleanupInterval; } public int getMaxUnreplicatedInterval() { return maxUnreplicatedInterval; } public void setMaxUnreplicatedInterval(int maxUnreplicatedInterval) { this.maxUnreplicatedInterval = maxUnreplicatedInterval; } public boolean isStarted() { return started; } // --------------------------------------------------------- Public Methods // ------------------------------------------------------- PersistentStore public void clear() { RuntimeException exception = null; int numberOfTries = 2; while (numberOfTries-- > 0) { Connection _conn = safeGetConnection(); boolean success = false; try { PreparedStatement preparedClearSql = prepareStatement(_conn, getClearSql()); preparedClearSql.setString(1, getName()); preparedClearSql.execute(); _conn.commit(); success = true; exception = null; break; } catch (SQLException e) { if (exception == null) { exception = new RuntimeException("Caught SQLException executing store clear", e); } } catch (RuntimeException e) { if (exception == null) { exception = e; } } finally { try { if (!success) { cleanup(_conn, null, true); } } finally { releaseConnection(_conn); } } } if (exception != null) { throw exception; } } public int getSize() { int size = 0; ResultSet rst = null; RuntimeException exception = null; int numberOfTries = 2; while (numberOfTries-- > 0) { Connection _conn = safeGetConnection(); boolean success = false; try { PreparedStatement preparedSizeSql = prepareStatement(_conn, getSizeSql()); preparedSizeSql.setString(1, getName()); rst = preparedSizeSql.executeQuery(); if (rst.next()) { size = rst.getInt(1); } _conn.commit(); success = true; exception = null; break; } catch (SQLException e) { if (exception == null) { exception = new RuntimeException("Caught SQLException getting store size", e); } } catch (RuntimeException e) { if (exception == null) { exception = e; } } finally { try { if (!success) { cleanup(_conn, rst, true); } else if (rst != null) { rst.close(); } } catch (SQLException e) { ; } finally { releaseConnection(_conn); } } } if (exception != null) { throw exception; } return (size); } public Set<String> getSessionIds() { ResultSet rst = null; Set<String> keys = null; RuntimeException exception = null; int numberOfTries = 2; while (numberOfTries-- > 0) { Connection _conn = safeGetConnection(); boolean success = true; try { PreparedStatement preparedKeysSql = prepareStatement(_conn, getKeysSql()); preparedKeysSql.setString(1, getName()); rst = preparedKeysSql.executeQuery(); keys = new HashSet<String>(); if (rst != null) { while (rst.next()) { keys.add(rst.getString(1)); } } _conn.commit(); success = true; exception = null; break; } catch (SQLException e) { if (exception == null) { exception = new RuntimeException("Caught SQLException getting session ids", e); } } catch (RuntimeException e) { if (exception == null) { exception = e; } } finally { try { if (!success) { cleanup(_conn, rst, true); } else if (rst != null) { rst.close(); } } catch (SQLException e) { ; } finally { releaseConnection(_conn); } } } if (exception != null) { throw exception; } return (keys); } public IncomingDistributableSessionData getSessionData(String realId, boolean includeAttributes) { ResultSet rst = null; IncomingDistributableSessionData incomingSession = null; ObjectInputStream attributes_ois = null; RuntimeException exception = null; int numberOfTries = 2; while (numberOfTries-- > 0) { Connection _conn = safeGetConnection(); boolean success = false; try { String sql = includeAttributes ? getFullLoadSql() : getPartialLoadSql(); PreparedStatement preparedLoadSql = prepareStatement(_conn, sql); preparedLoadSql.setString(1, realId); preparedLoadSql.setString(2, getName()); rst = preparedLoadSql.executeQuery(); if (rst.next()) { if (getLogger().isTraceEnabled()) { getLogger().trace("Loading session " + maskId(realId)); } DistributableSessionMetadata metadata = new DistributableSessionMetadata(); metadata.setId(rst.getString(1)); metadata.setCreationTime(rst.getLong(2)); String isNew = rst.getString(3); metadata.setNew("1".equals(isNew)); metadata.setMaxInactiveInterval(rst.getInt(4)); String valid = rst.getString(7); metadata.setValid("1".equals(valid)); // metadata.setValid(true); Integer version = Integer.valueOf(rst.getInt(5)); Long timestamp = Long.valueOf(rst.getLong(6)); Map<String, Object> attributes = null; if (includeAttributes) { BufferedInputStream attributes_bis = new BufferedInputStream(rst.getBinaryStream(8)); attributes_ois = new ObjectInputStream(attributes_bis); SimpleCachableMarshalledValue mv = (SimpleCachableMarshalledValue) attributes_ois.readObject(); mv.setObjectStreamSource(SessionSerializationFactory.getObjectStreamSource()); attributes = uncheckedCast(mv.get()); } incomingSession = new IncomingDistributableSessionDataImpl(version, timestamp, metadata, attributes); } else if (getLogger().isTraceEnabled()) { getLogger().trace(getStoreName() + ": No persisted data object found"); } _conn.commit(); success = true; exception = null; break; } catch (SQLException e) { if (exception == null) { exception = new RuntimeException("Caught SQLException loading session " + maskId(realId), e); } } catch (IOException e) { if (exception == null) { exception = new RuntimeException("Caught IOException loading session " + maskId(realId), e); } } catch (ClassNotFoundException e) { if (exception == null) { exception = new RuntimeException("Caught ClassNotFoundException loading session " + maskId(realId), e); } } catch (RuntimeException e) { if (exception == null) { exception = e; } } finally { try { if (!success) { cleanup(_conn, rst, true); } else if (rst != null) { rst.close(); } if (attributes_ois != null) { try { attributes_ois.close(); } catch (IOException e) { ; } } } catch (SQLException e) { ; } finally { releaseConnection(_conn); } } } if (exception != null) { throw exception; } return (incomingSession); } public void remove(String realId) { if (getLogger().isTraceEnabled()) { getLogger().trace("Loading session " + maskId(realId)); } RuntimeException exception = null; int numberOfTries = 2; while (numberOfTries-- > 0) { Connection _conn = safeGetConnection(); boolean success = false; try { executeRemove(realId, _conn); _conn.commit(); success = true; exception = null; break; } catch (SQLException e) { if (exception == null) { exception = new RuntimeException("Caught SQLException removing session " + maskId(realId), e); } } catch (RuntimeException e) { if (exception == null) { exception = e; } } finally { try { if (!success) { cleanup(_conn, null, true); } } finally { releaseConnection(_conn); } } } if (exception != null) { throw exception; } } public void storeSessionData(OutgoingSessionGranularitySessionData sessionData) { if (getLogger().isTraceEnabled()) { getLogger().trace("Storing session " + maskId(sessionData)); } RuntimeException exception = null; ObjectOutputStream oos = null; int numberOfTries = 2; while (numberOfTries-- > 0) { boolean success = false; Connection _conn = safeGetConnection(); if (_conn == null) { return; } try { byte[] obs = writeSessionAttributes(sessionData); DistributableSessionMetadata metadata = sessionData.getMetadata(); if (metadata != null && metadata.isNew()) { try { executeInsert(sessionData, obs, _conn); } catch (SQLException e) { // See if this is due to pre-existing record if (getLogger().isTraceEnabled()) { getLogger().trace("Caught SQLException inserting session " + maskId(sessionData), e); } // The existing connection is no good now; need a new one cleanup(_conn, null, true); _conn = null; _conn = safeGetConnection(); if (obs != null && executeGetSessionVersion(_conn, sessionData.getRealId()) != null) { executeReInsert(sessionData, obs, _conn); } else { throw e; } } } else { int count = executeUpdate(sessionData, obs, _conn); if (count < 1) { // For whatever reason this doesn't exist if (metadata != null && obs != null) { executeInsert(sessionData, obs, _conn); } else { // Hmm, we don't have enough data for a full insert throw new IllegalStateException("Cannot insert session " + maskId(sessionData) + " as session metadata is not available"); } } } _conn.commit(); success = true; exception = null; break; } catch (SQLException e) { if (exception == null) { exception = new RuntimeException("Caught SQLException storing session " + maskId(sessionData), e); } } catch (IOException e) { if (exception == null) { exception = new RuntimeException("Caught IOException storing session " + maskId(sessionData), e); } } catch (RuntimeException e) { if (exception == null) { exception = e; } } finally { try { if (!success) { cleanup(_conn, null, true); } if (oos != null) { try { oos.close(); } catch (IOException ignored) { ; } } } finally { releaseConnection(_conn); } } } if (exception != null) { throw exception; } } private static String maskId(OutgoingSessionGranularitySessionData sessionData) { String realId = (sessionData == null ? null : sessionData.getRealId()); return maskId(realId); } private static String maskId(String realId) { if (realId == null) { return null; } else { int length = realId.length(); if (length <= 8) { return realId; } StringBuilder sb = new StringBuilder(realId.substring(0, 2)); sb.append("****"); sb.append(realId.substring(length - 6, length)); return sb.toString(); } } public Long getSessionTimestamp(String realId) { ResultSet rst = null; Long result = null; RuntimeException exception = null; int numberOfTries = 2; while (numberOfTries-- > 0) { boolean success = false; Connection _conn = safeGetConnection(); try { PreparedStatement preparedTimestampSql = prepareStatement(_conn, getTimestampSql()); preparedTimestampSql.setString(1, realId); preparedTimestampSql.setString(2, getName()); rst = preparedTimestampSql.executeQuery(); if (rst.next()) { result = Long.valueOf(rst.getLong(1)); } _conn.commit(); success = true; exception = null; break; } catch (SQLException e) { if (exception == null) { exception = new RuntimeException("Caught SQLException getting timestamp for session " + maskId(realId), e); } } catch (RuntimeException e) { if (exception == null) { exception = e; } } finally { try { if (!success) { cleanup(_conn, rst, true); } else if (rst != null) { try { rst.close(); } catch (SQLException e) { ; } } } finally { releaseConnection(_conn); } } } if (exception != null) { throw exception; } return result; } public Integer getSessionVersion(String realId) { Integer result = null; RuntimeException exception = null; int numberOfTries = 2; while (numberOfTries-- > 0) { boolean success = false; Connection _conn = safeGetConnection(); try { result = executeGetSessionVersion(_conn, realId); _conn.commit(); success = true; exception = null; break; } catch (SQLException e) { if (exception == null) { exception = new RuntimeException("Caught SQLException getting version for session " + maskId(realId), e); } } catch (RuntimeException e) { if (exception == null) { exception = e; } } finally { try { if (!success) { // cleanup(_conn, rst, true); cleanup(_conn, null, true); } // else if (rst != null) // { // try // { // rst.close(); // } // catch (SQLException e) // { // ; // } // } } finally { releaseConnection(_conn); } } } if (exception != null) { throw exception; } return result; } public void processExpires() { long now = System.currentTimeMillis(); long interval = cleanupInterval * 1000; long earliest = now - interval; if (earliest > lastCleanup) { long maxUnrep = this.maxUnreplicatedInterval < 0 ? 60000 : this.maxUnreplicatedInterval * 1000; Connection _conn = safeGetConnection(); boolean success = false; try { PreparedStatement preparedCleanupSql = prepareStatement(_conn, getCleanupSql()); preparedCleanupSql.setString(1, getName()); preparedCleanupSql.setLong(2, earliest); preparedCleanupSql.setLong(3, (now - maxUnrep)); preparedCleanupSql.execute(); _conn.commit(); lastCleanup = now; success = true; } catch (Exception e) { getLogger().error("Caught exception cleaning out expired sessions", e); } finally { try { if (!success) { cleanup(_conn, null, true); } } finally { releaseConnection(_conn); } } } } public void start() { // Validate and update our current component state if (started) throw new IllegalStateException(getStoreName() + " is already started"); getName(); createSql(); startStore(); started = true; } public void stop() { // Validate and update our current component state if (!started) { throw new IllegalStateException(getStoreName() + " is not started"); } started = false; } // -------------------------------------------------------------- Protected /** * Hook for subclasses to perform any needed startup work. */ protected abstract void startStore(); /** * Returns a connection. Calls to this method * must be paired (typically via a try/finally block) with a call * to {@link #releaseConnection(Connection)}. * * @return the connection * * @throws SQLException if a database access error occurs * @throws RuntimeException if a connection could not be obtained */ protected abstract Connection getConnection() throws SQLException; /** * Releases a connection obtained from {@link #getConnection()}. * * @param conn the connection */ protected abstract void releaseConnection(Connection conn); /** * Clean up a connection, any associated statements, and an associated * result set. * * @param conn the connection. Null is handled but isn't sensible * @param resultSet the result set, which may be null * @param rollback whether {@link Connection#rollback()} should be invoked on the connection */ protected void cleanup(Connection conn, ResultSet resultSet, boolean rollback) { if (conn != null) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { getLogger().warn("Caught SQLException closing a result set -- " + e.getLocalizedMessage()); // Just log it here } } if (rollback) { try { conn.rollback(); } catch (SQLException e) { if (getLogger().isTraceEnabled()) { getLogger().trace("Caught SQLException rolling back connection -- " + e.getLocalizedMessage(), e); } } } Set<Statement> stmts = statementsByConnection.remove(conn); if (stmts != null) { for (Statement stmt : stmts) { try { stmt.close(); } catch (SQLException e) { getLogger().debug("Caught SQLException closing statement -- " + e.getLocalizedMessage()); } } } // Close this database connection, and log any errors try { conn.close(); } catch (SQLException e) { getLogger().error("Caught SQLException closing connection -- " + e.getLocalizedMessage()); // Just log it here } } } protected Logger getLogger() { return logger == null ? LOG : logger; } // ---------------------------------------------------------------- Private /** * Establishes the SQL strings returned by the various <code>getXyzSql()</code> * methods. */ private void createSql() { this.clearSql = "DELETE FROM " + getSessionTable() + " WHERE " + getSessionAppCol() + " = ?"; this.keysSql = "SELECT " + getSessionIdCol() + " FROM " + getSessionTable() + " WHERE " + getSessionAppCol() + " = ?"; this.sizeSql = "SELECT COUNT(" + getSessionIdCol() + ") " + " FROM " + getSessionTable() + " WHERE " + getSessionAppCol() + " = ?"; this.fullLoadSql = "SELECT " + getSessionFullIdCol() + ", " + getSessionCreationTimeCol() + ", " + getSessionNewCol() + ", " + getSessionMaxInactiveCol() + ", " + getSessionVersionCol() + ", " + getSessionLastAccessedCol() + ", " + getSessionValidCol() + ", " + getSessionAttributeCol() + " FROM " + getSessionTable() + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.partialLoadSql = "SELECT " + getSessionFullIdCol() + ", " + getSessionCreationTimeCol() + ", " + getSessionNewCol() + ", " + getSessionMaxInactiveCol() + ", " + getSessionVersionCol() + ", " + getSessionLastAccessedCol() + ", " + getSessionValidCol() + " FROM " + getSessionTable() + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.removeSql = "DELETE FROM " + getSessionTable() + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.insertSql = "INSERT INTO " + getSessionTable() + " (" + getSessionAppCol() + ", " + getSessionIdCol() + ", " + getSessionFullIdCol() + ", " + getSessionCreationTimeCol() + ", " + getSessionNewCol() + ", " + getSessionMaxInactiveCol() + ", " + getSessionVersionCol() + ", " + getSessionLastAccessedCol() + ", " + getSessionValidCol() + ", " + getSessionAttributeCol() + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; this.simpleUpdateSql = "UPDATE " + getSessionTable() + " SET " + getSessionVersionCol() + " = ?, " + getSessionLastAccessedCol() + " = ?" + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.attributeUpdateSql = "UPDATE " + getSessionTable() + " SET " + getSessionVersionCol() + " = ?, " + getSessionLastAccessedCol() + " = ?, " + getSessionAttributeCol() + " = ?" + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.metadataUpdateSql = "UPDATE " + getSessionTable() + " SET " + getSessionVersionCol() + " = ?, " + getSessionLastAccessedCol() + " = ?, " + getSessionFullIdCol() + " = ?, " + getSessionNewCol() + " = ?, " + getSessionMaxInactiveCol() + " = ?, " + getSessionValidCol() + " = ?" + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.fullUpdateSql = "UPDATE " + getSessionTable() + " SET " + getSessionVersionCol() + " = ?, " + getSessionLastAccessedCol() + " = ?, " + getSessionFullIdCol() + " = ?, " + getSessionNewCol() + " = ?, " + getSessionMaxInactiveCol() + " = ?, " + getSessionValidCol() + " = ?, " + getSessionAttributeCol() + " = ?" + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.reinsertSql = "UPDATE " + getSessionTable() + " SET " + getSessionVersionCol() + " = ?, " + getSessionLastAccessedCol() + " = ?, " + getSessionFullIdCol() + " = ?, " + getSessionNewCol() + " = ?, " + getSessionMaxInactiveCol() + " = ?, " + getSessionValidCol() + " = ?, " + getSessionAttributeCol() + " = ?, " + getSessionCreationTimeCol() + " = ? WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.timestampSql = "SELECT " + getSessionLastAccessedCol() + " FROM " + getSessionTable() + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.versionSql = "SELECT " + getSessionVersionCol() + " FROM " + getSessionTable() + " WHERE " + getSessionIdCol() + " = ? AND " + getSessionAppCol() + " = ?"; this.cleanupSql = "DELETE FROM " + getSessionTable() + " WHERE " + getSessionAppCol() + " = ?" + " AND " + getSessionLastAccessedCol() + " < ? AND " + getSessionLastAccessedCol() + " < (? - (" + getSessionMaxInactiveCol() + " * 1000))"; } private void executeRemove(String id, Connection _conn) throws SQLException { PreparedStatement preparedRemoveSql = prepareStatement(_conn, getRemoveSql()); preparedRemoveSql.setString(1, id); preparedRemoveSql.setString(2, getName()); preparedRemoveSql.execute(); } private void executeInsert(OutgoingSessionGranularitySessionData session, byte[] obs, Connection conn) throws SQLException, IOException { if (obs == null) { obs = this.emptyAttributes; } DistributableSessionMetadata metadata = session.getMetadata(); if (metadata == null) { throw new IllegalStateException("Cannot insert session " + maskId(session) + " as session metadata is missing"); } int size = obs.length; ByteArrayInputStream bis = new ByteArrayInputStream(obs); InputStream in = new BufferedInputStream(bis, size); try { PreparedStatement preparedInsertSql = prepareStatement(conn, getInsertSql()); preparedInsertSql.setString(1, getName()); preparedInsertSql.setString(2, session.getRealId()); preparedInsertSql.setString(3, metadata.getId()); preparedInsertSql.setLong(4, metadata.getCreationTime()); preparedInsertSql.setString(5, metadata.isNew() ? "1" : "0"); preparedInsertSql.setInt(6, metadata.getMaxInactiveInterval()); preparedInsertSql.setInt(7, session.getVersion()); preparedInsertSql.setLong(8, session.getTimestamp()); preparedInsertSql.setString(9, metadata.isValid() ? "1" : "0"); preparedInsertSql.setBinaryStream(10, in, size); preparedInsertSql.execute(); } finally { in.close(); } } private int executeReInsert(OutgoingSessionGranularitySessionData session, byte[] obs, Connection conn) throws SQLException, IOException { DistributableSessionMetadata metadata = session.getMetadata(); int size = obs.length; InputStream in = null; if (obs != null) { ByteArrayInputStream bis = new ByteArrayInputStream(obs); in = new BufferedInputStream(bis, size); } try { PreparedStatement preparedUpdateSql = prepareStatement(conn, getReInsertSql()); preparedUpdateSql.setInt(1, session.getVersion()); preparedUpdateSql.setLong(2, session.getTimestamp()); preparedUpdateSql.setString(3, metadata.getId()); preparedUpdateSql.setString(4, metadata.isNew() ? "1" : "0"); preparedUpdateSql.setInt(5, metadata.getMaxInactiveInterval()); preparedUpdateSql.setString(6, metadata.isValid() ? "1" : "0"); preparedUpdateSql.setBinaryStream(7, in, size); preparedUpdateSql.setLong(8, metadata.getCreationTime()); // Add in the WHERE clause params preparedUpdateSql.setString(9, session.getRealId()); preparedUpdateSql.setString(10, getName()); int count = preparedUpdateSql.executeUpdate(); return count; } finally { if (in != null) { in.close(); } } } private int executeUpdate(OutgoingSessionGranularitySessionData session, byte[] obs, Connection conn) throws SQLException, IOException { DistributableSessionMetadata metadata = session.getMetadata(); int size = obs == null ? -1 : obs.length; InputStream in = null; if (obs != null) { ByteArrayInputStream bis = new ByteArrayInputStream(obs); in = new BufferedInputStream(bis, size); } try { PreparedStatement preparedUpdateSql = null; int idParam = -1; // first parameter in the WHERE clause if (metadata != null) { if (obs != null) { preparedUpdateSql = prepareStatement(conn, getFullUpdateSql()); preparedUpdateSql.setBinaryStream(7, in, size); idParam = 8; } else { preparedUpdateSql = prepareStatement(conn, getMetadataUpdateSql()); idParam = 7; } preparedUpdateSql.setString(3, metadata.getId()); preparedUpdateSql.setString(4, metadata.isNew() ? "1" : "0"); preparedUpdateSql.setInt(5, metadata.getMaxInactiveInterval()); preparedUpdateSql.setString(6, metadata.isValid() ? "1" : "0"); } else if (obs != null) { preparedUpdateSql = prepareStatement(conn, getAttributeUpdateSql()); preparedUpdateSql.setBinaryStream(3, in, size); idParam = 4; } else { preparedUpdateSql = prepareStatement(conn, getSimpleUpdateSql()); idParam = 3; } // Add in the version and timestamp preparedUpdateSql.setInt(1, session.getVersion()); preparedUpdateSql.setLong(2, session.getTimestamp()); // Add in the WHERE clause params preparedUpdateSql.setString(idParam, session.getRealId()); preparedUpdateSql.setString(idParam + 1, getName()); int count = preparedUpdateSql.executeUpdate(); return count; } finally { if (in != null) { in.close(); } } } private Integer executeGetSessionVersion(Connection _conn, String realId) throws SQLException { PreparedStatement preparedTimestampSql = prepareStatement(_conn, getVersionSql()); preparedTimestampSql.setString(1, realId); preparedTimestampSql.setString(2, getName()); ResultSet rst = null; try { Integer result = null; rst = preparedTimestampSql.executeQuery(); if (rst.next()) { result = Integer.valueOf(rst.getInt(1)); } return result; } finally { if (rst != null) { rst.close(); } } } private String getCleanupSql() { return cleanupSql; } private String getClearSql() { return clearSql; } private String getInsertSql() { return insertSql; } private String getFullUpdateSql() { return fullUpdateSql; } private String getReInsertSql() { return reinsertSql; } private String getSimpleUpdateSql() { return simpleUpdateSql; } private String getMetadataUpdateSql() { return metadataUpdateSql; } private String getAttributeUpdateSql() { return attributeUpdateSql; } private String getKeysSql() { return keysSql; } private String getFullLoadSql() { return fullLoadSql; } private String getPartialLoadSql() { return this.partialLoadSql; } private String getRemoveSql() { return removeSql; } private String getSizeSql() { return sizeSql; } private String getVersionSql() { return versionSql; } private String getTimestampSql() { return timestampSql; } private Connection safeGetConnection() { try { return getConnection(); } catch (SQLException e) { throw new RuntimeException("Caught SQLException getting a connection", e); } } private PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException { Set<Statement> stmts = statementsByConnection.get(conn); if (stmts == null) { stmts = new HashSet<Statement>(); statementsByConnection.put(conn, stmts); } PreparedStatement stmt = conn.prepareStatement(sql); stmts.add(stmt); return stmt; } private byte[] writeSessionAttributes(OutgoingSessionGranularitySessionData session) throws IOException { Map<String, Object> attrs = session.getSessionAttributes(); if (attrs == null) { return null; } ObjectOutputStream oos = null; ByteArrayOutputStream bos = null; try { bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(new BufferedOutputStream(bos)); oos.writeObject(new SimpleCachableMarshalledValue((Serializable) attrs)); oos.close(); return bos.toByteArray(); } finally { if (oos != null) { oos.close(); } if (bos != null) { bos.close(); } } } @SuppressWarnings("unchecked") private static <T> T uncheckedCast(Object obj) { return (T) obj; } }