All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.server.session.JDBCSessionDataStore Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.server.session;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * JDBCSessionDataStore
 *
 * Session data stored in database
 */
@ManagedObject
public class JDBCSessionDataStore extends AbstractSessionDataStore
{
    private static final Logger LOG = LoggerFactory.getLogger(JDBCSessionDataStore.class);

    /**
     * Used for Oracle and other databases where "" is treated as NULL
     */
    public static final String NULL_CONTEXT_PATH = "/";

    protected boolean _initialized = false;
    protected DatabaseAdaptor _dbAdaptor;
    protected SessionTableSchema _sessionTableSchema;
    protected boolean _schemaProvided;

    private static final ByteArrayInputStream EMPTY = new ByteArrayInputStream(new byte[0]);

    /**
     * SessionTableSchema
     */
    public static class SessionTableSchema
    {
        public static final int MAX_INTERVAL_NOT_SET = -999;
        public static final String INFERRED = "INFERRED";

        protected DatabaseAdaptor _dbAdaptor;
        protected String _schemaName = null;
        protected String _catalogName = null;
        protected String _tableName = "JettySessions";
        protected String _idColumn = "sessionId";
        protected String _contextPathColumn = "contextPath";
        protected String _virtualHostColumn = "virtualHost";
        protected String _lastNodeColumn = "lastNode";
        protected String _accessTimeColumn = "accessTime";
        protected String _lastAccessTimeColumn = "lastAccessTime";
        protected String _createTimeColumn = "createTime";
        protected String _cookieTimeColumn = "cookieTime";
        protected String _lastSavedTimeColumn = "lastSavedTime";
        protected String _expiryTimeColumn = "expiryTime";
        protected String _maxIntervalColumn = "maxInterval";
        protected String _mapColumn = "map";

        protected void setDatabaseAdaptor(DatabaseAdaptor dbadaptor)
        {
            _dbAdaptor = dbadaptor;
        }
        
        public void setCatalogName(String catalogName)
        {
            if (catalogName != null && StringUtil.isBlank(catalogName))
                _catalogName = null;
            else
                _catalogName = catalogName;
        }

        public String getCatalogName()
        {
            return _catalogName;
        }
        
        public String getSchemaName()
        {
            return _schemaName;
        }

        public void setSchemaName(String schemaName)
        {
            if (schemaName != null && StringUtil.isBlank(schemaName))
                _schemaName = null;
            else
                _schemaName = schemaName;
        }

        public String getTableName()
        {
            return _tableName;
        }

        public void setTableName(String tableName)
        {
            checkNotNull(tableName);
            _tableName = tableName;
        }

        private String getSchemaTableName()
        {
            return (getSchemaName() != null ? getSchemaName() + "." : "") + getTableName();
        }

        public String getIdColumn()
        {
            return _idColumn;
        }

        public void setIdColumn(String idColumn)
        {
            checkNotNull(idColumn);
            _idColumn = idColumn;
        }

        public String getContextPathColumn()
        {
            return _contextPathColumn;
        }

        public void setContextPathColumn(String contextPathColumn)
        {
            checkNotNull(contextPathColumn);
            _contextPathColumn = contextPathColumn;
        }

        public String getVirtualHostColumn()
        {
            return _virtualHostColumn;
        }

        public void setVirtualHostColumn(String virtualHostColumn)
        {
            checkNotNull(virtualHostColumn);
            _virtualHostColumn = virtualHostColumn;
        }

        public String getLastNodeColumn()
        {
            return _lastNodeColumn;
        }

        public void setLastNodeColumn(String lastNodeColumn)
        {
            checkNotNull(lastNodeColumn);
            _lastNodeColumn = lastNodeColumn;
        }

        public String getAccessTimeColumn()
        {
            return _accessTimeColumn;
        }

        public void setAccessTimeColumn(String accessTimeColumn)
        {
            checkNotNull(accessTimeColumn);
            _accessTimeColumn = accessTimeColumn;
        }

        public String getLastAccessTimeColumn()
        {
            return _lastAccessTimeColumn;
        }

        public void setLastAccessTimeColumn(String lastAccessTimeColumn)
        {
            checkNotNull(lastAccessTimeColumn);
            _lastAccessTimeColumn = lastAccessTimeColumn;
        }

        public String getCreateTimeColumn()
        {
            return _createTimeColumn;
        }

        public void setCreateTimeColumn(String createTimeColumn)
        {
            checkNotNull(createTimeColumn);
            _createTimeColumn = createTimeColumn;
        }

        public String getCookieTimeColumn()
        {
            return _cookieTimeColumn;
        }

        public void setCookieTimeColumn(String cookieTimeColumn)
        {
            checkNotNull(cookieTimeColumn);
            _cookieTimeColumn = cookieTimeColumn;
        }

        public String getLastSavedTimeColumn()
        {
            return _lastSavedTimeColumn;
        }

        public void setLastSavedTimeColumn(String lastSavedTimeColumn)
        {
            checkNotNull(lastSavedTimeColumn);
            _lastSavedTimeColumn = lastSavedTimeColumn;
        }

        public String getExpiryTimeColumn()
        {
            return _expiryTimeColumn;
        }

        public void setExpiryTimeColumn(String expiryTimeColumn)
        {
            checkNotNull(expiryTimeColumn);
            _expiryTimeColumn = expiryTimeColumn;
        }

        public String getMaxIntervalColumn()
        {
            return _maxIntervalColumn;
        }

        public void setMaxIntervalColumn(String maxIntervalColumn)
        {
            checkNotNull(maxIntervalColumn);
            _maxIntervalColumn = maxIntervalColumn;
        }

        public String getMapColumn()
        {
            return _mapColumn;
        }

        public void setMapColumn(String mapColumn)
        {
            checkNotNull(mapColumn);
            _mapColumn = mapColumn;
        }

        public String getCreateStatementAsString()
        {
            if (_dbAdaptor == null)
                throw new IllegalStateException("No DBAdaptor");

            String blobType = _dbAdaptor.getBlobType();
            String longType = _dbAdaptor.getLongType();
            String stringType = _dbAdaptor.getStringType();

            return "create table " + getSchemaTableName() + " (" + _idColumn + " " + stringType + "(120), " +
                _contextPathColumn + " " + stringType + "(60), " + _virtualHostColumn + " " + stringType + "(60), " + _lastNodeColumn + " " + stringType + "(60), " + _accessTimeColumn + " " + longType + ", " +
                _lastAccessTimeColumn + " " + longType + ", " + _createTimeColumn + " " + longType + ", " + _cookieTimeColumn + " " + longType + ", " +
                _lastSavedTimeColumn + " " + longType + ", " + _expiryTimeColumn + " " + longType + ", " + _maxIntervalColumn + " " + longType + ", " +
                _mapColumn + " " + blobType + ", primary key(" + _idColumn + ", " + _contextPathColumn + "," + _virtualHostColumn + "))";
        }

        public String getCreateIndexOverExpiryStatementAsString(String indexName)
        {
            return "create index " + indexName + " on " + getSchemaTableName() + " (" + getExpiryTimeColumn() + ")";
        }

        public String getCreateIndexOverSessionStatementAsString(String indexName)
        {
            return "create index " + indexName + " on " + getSchemaTableName() + " (" + getIdColumn() + ", " + getContextPathColumn() + ")";
        }

        public String getAlterTableForMaxIntervalAsString()
        {
            if (_dbAdaptor == null)
                throw new IllegalStateException("No DBAdaptor");
            String longType = _dbAdaptor.getLongType();
            String stem = "alter table " + getSchemaTableName() + " add " + getMaxIntervalColumn() + " " + longType;
            if (_dbAdaptor.getDBName().contains("oracle"))
                return stem + " default " + MAX_INTERVAL_NOT_SET + " not null";
            else
                return stem + " not null default " + MAX_INTERVAL_NOT_SET;
        }

        private void checkNotNull(String s)
        {
            if (s == null)
                throw new IllegalArgumentException(s);
        }

        public String getInsertSessionStatementAsString()
        {
            return "insert into " + getSchemaTableName() +
                " (" + getIdColumn() + ", " + getContextPathColumn() + ", " + getVirtualHostColumn() + ", " + getLastNodeColumn() +
                ", " + getAccessTimeColumn() + ", " + getLastAccessTimeColumn() + ", " + getCreateTimeColumn() + ", " + getCookieTimeColumn() +
                ", " + getLastSavedTimeColumn() + ", " + getExpiryTimeColumn() + ", " + getMaxIntervalColumn() + ", " + getMapColumn() + ") " +
                " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
        }

        public PreparedStatement getUpdateSessionStatement(Connection connection, String id, SessionContext context)
            throws SQLException
        {
            String s = "update " + getSchemaTableName() +
                " set " + getLastNodeColumn() + " = ?, " + getAccessTimeColumn() + " = ?, " +
                getLastAccessTimeColumn() + " = ?, " + getLastSavedTimeColumn() + " = ?, " + getExpiryTimeColumn() + " = ?, " +
                getMaxIntervalColumn() + " = ?, " + getMapColumn() + " = ? where " + getIdColumn() + " = ? and " + getContextPathColumn() +
                " = ? and " + getVirtualHostColumn() + " = ?";

            String cp = context.getCanonicalContextPath();
            if (_dbAdaptor.isEmptyStringNull() && StringUtil.isBlank(cp))
                cp = NULL_CONTEXT_PATH;

            PreparedStatement statement = connection.prepareStatement(s);
            statement.setString(8, id);
            statement.setString(9, cp);
            statement.setString(10, context.getVhost());
            return statement;
        }

        public PreparedStatement getExpiredSessionsStatement(Connection connection, String canonicalContextPath, String vhost, long expiry)
            throws SQLException
        {
            // TODO expiry should be a delay rather than an absolute time.

            if (_dbAdaptor == null)
                throw new IllegalStateException("No DB adaptor");

            String cp = canonicalContextPath;
            if (_dbAdaptor.isEmptyStringNull() && StringUtil.isBlank(cp))
                cp = NULL_CONTEXT_PATH;

            PreparedStatement statement = connection.prepareStatement("select " + getIdColumn() + ", " + getExpiryTimeColumn() +
                " from " + getSchemaTableName() + " where " + getContextPathColumn() + " = ? and " +
                getVirtualHostColumn() + " = ? and " +
                getExpiryTimeColumn() + " >0 and " + getExpiryTimeColumn() + " <= ?");

            statement.setString(1, cp);
            statement.setString(2, vhost);
            statement.setLong(3, expiry);
            return statement;
        }

        public PreparedStatement getMyExpiredSessionsStatement(Connection connection, SessionContext sessionContext, long expiry)
            throws SQLException
        {
            // TODO expiry should be a delay rather than an absolute time.

            if (_dbAdaptor == null)
                throw new IllegalStateException("No DB adaptor");

            String cp = sessionContext.getCanonicalContextPath();
            if (_dbAdaptor.isEmptyStringNull() && StringUtil.isBlank(cp))
                cp = NULL_CONTEXT_PATH;

            PreparedStatement statement = connection.prepareStatement("select " + getIdColumn() + ", " + getExpiryTimeColumn() +
                " from " + getSchemaTableName() + " where " +
                getLastNodeColumn() + " = ? and " +
                getContextPathColumn() + " = ? and " +
                getVirtualHostColumn() + " = ? and " +
                getExpiryTimeColumn() + " >0 and " + getExpiryTimeColumn() + " <= ?");

            statement.setString(1, sessionContext.getWorkerName());
            statement.setString(2, cp);
            statement.setString(3, sessionContext.getVhost());
            statement.setLong(4, expiry);
            return statement;
        }
    
        public PreparedStatement getCheckSessionExistsStatement(Connection connection, SessionContext context)
            throws SQLException
        {
            if (_dbAdaptor == null)
                throw new IllegalStateException("No DB adaptor");

            String cp = context.getCanonicalContextPath();
            if (_dbAdaptor.isEmptyStringNull() && StringUtil.isBlank(cp))
                cp = NULL_CONTEXT_PATH;

            PreparedStatement statement = connection.prepareStatement("select " + getIdColumn() + ", " + getExpiryTimeColumn() +
                " from " + getSchemaTableName() +
                " where " + getIdColumn() + " = ? and " +
                getContextPathColumn() + " = ? and " +
                getVirtualHostColumn() + " = ?");
            statement.setString(2, cp);
            statement.setString(3, context.getVhost());
            return statement;
        }

        public PreparedStatement getLoadStatement(Connection connection, String id, SessionContext contextId)
            throws SQLException
        {
            if (_dbAdaptor == null)
                throw new IllegalStateException("No DB adaptor");

            String cp = contextId.getCanonicalContextPath();
            if (_dbAdaptor.isEmptyStringNull() && StringUtil.isBlank(cp))
                cp = NULL_CONTEXT_PATH;

            PreparedStatement statement = connection.prepareStatement("select * from " + getSchemaTableName() +
                " where " + getIdColumn() + " = ? and " + getContextPathColumn() +
                " = ? and " + getVirtualHostColumn() + " = ?");
            statement.setString(1, id);
            statement.setString(2, cp);
            statement.setString(3, contextId.getVhost());

            return statement;
        }

        public PreparedStatement getUpdateStatement(Connection connection, String id, SessionContext contextId)
            throws SQLException
        {
            if (_dbAdaptor == null)
                throw new IllegalStateException("No DB adaptor");

            String cp = contextId.getCanonicalContextPath();
            if (_dbAdaptor.isEmptyStringNull() && StringUtil.isBlank(cp))
                cp = NULL_CONTEXT_PATH;

            String s = "update " + getSchemaTableName() +
                " set " + getLastNodeColumn() + " = ?, " + getAccessTimeColumn() + " = ?, " +
                getLastAccessTimeColumn() + " = ?, " + getLastSavedTimeColumn() + " = ?, " + getExpiryTimeColumn() + " = ?, " +
                getMaxIntervalColumn() + " = ?, " + getMapColumn() + " = ? where " + getIdColumn() + " = ? and " + getContextPathColumn() +
                " = ? and " + getVirtualHostColumn() + " = ?";

            PreparedStatement statement = connection.prepareStatement(s);
            statement.setString(8, id);
            statement.setString(9, cp);
            statement.setString(10, contextId.getVhost());

            return statement;
        }

        public PreparedStatement getDeleteStatement(Connection connection, String id, SessionContext contextId)
            throws Exception
        {
            if (_dbAdaptor == null)

                throw new IllegalStateException("No DB adaptor");

            String cp = contextId.getCanonicalContextPath();
            if (_dbAdaptor.isEmptyStringNull() && StringUtil.isBlank(cp))
                cp = NULL_CONTEXT_PATH;

            PreparedStatement statement = connection.prepareStatement("delete from " + getSchemaTableName() +
                " where " + getIdColumn() + " = ? and " + getContextPathColumn() +
                " = ? and " + getVirtualHostColumn() + " = ?");
            statement.setString(1, id);
            statement.setString(2, cp);
            statement.setString(3, contextId.getVhost());

            return statement;
        }

        public PreparedStatement getCleanOrphansStatement(Connection connection, long timeLimit)
            throws Exception
        { 
            if (_dbAdaptor == null)
                throw new IllegalStateException("No DB adaptor");

            PreparedStatement statement = connection.prepareStatement("delete from " + getSchemaTableName() +
                " where " +
                getExpiryTimeColumn() + " > 0 and " + getExpiryTimeColumn() + " <= ?");
            statement.setLong(1, timeLimit);
            return statement;

        }

        /**
         * Set up the tables in the database
         *
         * @throws SQLException if unable to prepare tables
         */
        public void prepareTables()
            throws SQLException
        {
            try (Connection connection = _dbAdaptor.getConnection();
                 Statement statement = connection.createStatement())
            {
                //make the id table
                connection.setAutoCommit(true);
                DatabaseMetaData metaData = connection.getMetaData();
                _dbAdaptor.adaptTo(metaData);

                //make the session table if necessary
                String tableName = _dbAdaptor.convertIdentifier(getTableName());
                
                String schemaName = _dbAdaptor.convertIdentifier(getSchemaName());
                if (INFERRED.equalsIgnoreCase(schemaName))
                {
                    //use the value from the connection -
                    //NOTE that this value will also now be prepended to ALL
                    //table names in queries/updates.
                    schemaName = connection.getSchema();
                    setSchemaName(schemaName);
                }
                String catalogName = _dbAdaptor.convertIdentifier(getCatalogName());
                if (INFERRED.equalsIgnoreCase(catalogName))
                {
                    //use the value from the connection
                    catalogName = connection.getCatalog();
                    setCatalogName(catalogName);
                }
                
                try (ResultSet result = metaData.getTables(catalogName, schemaName, tableName, null))
                {
                    if (!result.next())
                    {
                        if (LOG.isDebugEnabled())
                            LOG.debug("Creating table {} schema={} catalog={}", tableName, schemaName, catalogName);
                        //table does not exist, so create it
                        statement.executeUpdate(getCreateStatementAsString());
                    }
                    else
                    {
                        if (LOG.isDebugEnabled())
                            LOG.debug("Not creating table {} schema={} catalog={}", tableName, schemaName, catalogName);
                        //session table exists, check it has maxinterval column
                        ResultSet colResult = null;
                        try
                        {
                            colResult = metaData.getColumns(catalogName, schemaName, tableName,
                                _dbAdaptor.convertIdentifier(getMaxIntervalColumn()));
                        }
                        catch (SQLException sqlEx)
                        {
                            LOG.warn("Problem checking if {} table contains {} column. Ensure table contains column with definition: long not null default -999",
                                getTableName(), getMaxIntervalColumn());
                            throw sqlEx;
                        }
                        try
                        {
                            if (!colResult.next())
                            {
                                try
                                {
                                    //add the maxinterval column
                                    statement.executeUpdate(getAlterTableForMaxIntervalAsString());
                                }
                                catch (SQLException sqlEx)
                                {
                                    LOG.warn("Problem adding {} column. Ensure table contains column definition: long not null default -999", getMaxIntervalColumn());
                                    throw sqlEx;
                                }
                            }
                        }
                        finally
                        {
                            colResult.close();
                        }
                    }
                }
                //make some indexes on the JettySessions table
                String index1 = "idx_" + getTableName() + "_expiry";
                String index2 = "idx_" + getTableName() + "_session";

                boolean index1Exists = false;
                boolean index2Exists = false;
                try (ResultSet result = metaData.getIndexInfo(catalogName, schemaName, tableName, false, true))
                {
                    while (result.next())
                    {
                        String idxName = result.getString("INDEX_NAME");
                        if (index1.equalsIgnoreCase(idxName))
                            index1Exists = true;
                        else if (index2.equalsIgnoreCase(idxName))
                            index2Exists = true;
                    }
                }
                if (!index1Exists)
                    statement.executeUpdate(getCreateIndexOverExpiryStatementAsString(index1));
                if (!index2Exists)
                    statement.executeUpdate(getCreateIndexOverSessionStatementAsString(index2));
            }
        }

        @Override
        public String toString()
        {
            return String.format("%s[%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s]", super.toString(),
                _catalogName, _schemaName, _tableName, _idColumn, _contextPathColumn, _virtualHostColumn, _cookieTimeColumn, _createTimeColumn,
                _expiryTimeColumn, _accessTimeColumn, _lastAccessTimeColumn, _lastNodeColumn, _lastSavedTimeColumn, _maxIntervalColumn);
        }
    }

    public JDBCSessionDataStore()
    {
        super();
    }

    @Override
    protected void doStart() throws Exception
    {
        if (_dbAdaptor == null)
            throw new IllegalStateException("No jdbc config");

        initialize();
        super.doStart();
    }

    @Override
    protected void doStop() throws Exception
    {
        super.doStop();
        _initialized = false;
        if (!_schemaProvided)
            _sessionTableSchema = null;
    }

    public void initialize() throws Exception
    {
        if (!_initialized)
        {
            _initialized = true;

            //taking the defaults if one not set
            if (_sessionTableSchema == null)
            {
                _sessionTableSchema = new SessionTableSchema();
                addBean(_sessionTableSchema, true);
            }

            _dbAdaptor.initialize();
            _sessionTableSchema.setDatabaseAdaptor(_dbAdaptor);
            _sessionTableSchema.prepareTables();
        }
    }

    @Override
    public SessionData doLoad(String id) throws Exception
    {
        try (Connection connection = _dbAdaptor.getConnection();
             PreparedStatement statement = _sessionTableSchema.getLoadStatement(connection, id, _context);
             ResultSet result = statement.executeQuery())
        {
            SessionData data = null;
            if (result.next())
            {
                data = newSessionData(id,
                    result.getLong(_sessionTableSchema.getCreateTimeColumn()),
                    result.getLong(_sessionTableSchema.getAccessTimeColumn()),
                    result.getLong(_sessionTableSchema.getLastAccessTimeColumn()),
                    result.getLong(_sessionTableSchema.getMaxIntervalColumn()));
                data.setCookieSet(result.getLong(_sessionTableSchema.getCookieTimeColumn()));
                data.setLastNode(result.getString(_sessionTableSchema.getLastNodeColumn()));
                data.setLastSaved(result.getLong(_sessionTableSchema.getLastSavedTimeColumn()));
                data.setExpiry(result.getLong(_sessionTableSchema.getExpiryTimeColumn()));
                data.setContextPath(_context.getCanonicalContextPath());
                data.setVhost(_context.getVhost());

                try (InputStream is = _dbAdaptor.getBlobInputStream(result, _sessionTableSchema.getMapColumn());
                     ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is))
                {
                    SessionData.deserializeAttributes(data, ois);
                }
                catch (Exception e)
                {
                    throw new UnreadableSessionDataException(id, _context, e);
                }

                if (LOG.isDebugEnabled())
                    LOG.debug("LOADED session {}", data);
            }
            else if (LOG.isDebugEnabled())
                LOG.debug("No session {}", id);

            return data;
        }
    }

    @Override
    public boolean delete(String id) throws Exception
    {
        try (Connection connection = _dbAdaptor.getConnection();
             PreparedStatement statement = _sessionTableSchema.getDeleteStatement(connection, id, _context))
        {
            connection.setAutoCommit(true);
            int rows = statement.executeUpdate();
            if (LOG.isDebugEnabled())
                LOG.debug("Deleted Session {}:{}", id, (rows > 0));

            return rows > 0;
        }
    }

    @Override
    public void doStore(String id, SessionData data, long lastSaveTime) throws Exception
    {
        if (data == null || id == null)
            return;

        if (lastSaveTime <= 0)
        {
            doInsert(id, data);
        }
        else
        {
            doUpdate(id, data);
        }
    }

    protected void doInsert(String id, SessionData data)
        throws Exception
    {
        String s = _sessionTableSchema.getInsertSessionStatementAsString();

        try (Connection connection = _dbAdaptor.getConnection())
        {
            connection.setAutoCommit(true);
            try (PreparedStatement statement = connection.prepareStatement(s))
            {
                statement.setString(1, id); //session id

                String cp = _context.getCanonicalContextPath();
                if (_dbAdaptor.isEmptyStringNull() && StringUtil.isBlank(cp))
                    cp = NULL_CONTEXT_PATH;

                statement.setString(2, cp); //context path

                statement.setString(3, _context.getVhost()); //first vhost
                statement.setString(4, data.getLastNode()); //my node id
                statement.setLong(5, data.getAccessed()); //accessTime
                statement.setLong(6, data.getLastAccessed()); //lastAccessTime
                statement.setLong(7, data.getCreated()); //time created
                statement.setLong(8, data.getCookieSet()); //time cookie was set
                statement.setLong(9, data.getLastSaved()); //last saved time
                statement.setLong(10, data.getExpiry());
                statement.setLong(11, data.getMaxInactiveMs());

                try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                     ObjectOutputStream oos = new ObjectOutputStream(baos))
                {
                    SessionData.serializeAttributes(data, oos);
                    byte[] bytes = baos.toByteArray();
                    ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
                    statement.setBinaryStream(12, bais, bytes.length); //attribute map as blob
                }

                statement.executeUpdate();
                if (LOG.isDebugEnabled())
                    LOG.debug("Inserted session {}", data);
            }
        }
    }

    protected void doUpdate(String id, SessionData data)
        throws Exception
    {
        try (Connection connection = _dbAdaptor.getConnection())
        {
            connection.setAutoCommit(true);
            try (PreparedStatement statement = _sessionTableSchema.getUpdateSessionStatement(connection, data.getId(), _context))
            {
                statement.setString(1, data.getLastNode()); //should be my node id
                statement.setLong(2, data.getAccessed()); //accessTime
                statement.setLong(3, data.getLastAccessed()); //lastAccessTime
                statement.setLong(4, data.getLastSaved()); //last saved time
                statement.setLong(5, data.getExpiry());
                statement.setLong(6, data.getMaxInactiveMs());

                try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                     ObjectOutputStream oos = new ObjectOutputStream(baos))
                {
                    SessionData.serializeAttributes(data, oos);
                    byte[] bytes = baos.toByteArray();
                    try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes))
                    {
                        statement.setBinaryStream(7, bais, bytes.length); //attribute map as blob
                    }
                }

                statement.executeUpdate();

                if (LOG.isDebugEnabled())
                    LOG.debug("Updated session {}", data);
            }
        }
    }

    @Override
    public Set doCheckExpired(Set candidates, long time)
    {
        if (LOG.isDebugEnabled())
            LOG.debug("Getting expired sessions at time {}", time);
        
        Set expiredSessionKeys = new HashSet<>();
        try (Connection connection = _dbAdaptor.getConnection())
        {
            connection.setAutoCommit(true);

            //Select sessions managed by this node for our context that have expired
            long upperBound = time;
            if (LOG.isDebugEnabled())
                LOG.debug("{} - Searching for sessions for context {} managed by me and expired before {}",  
                    _context.getWorkerName(), _context.getCanonicalContextPath(), upperBound);

            try (PreparedStatement statement = _sessionTableSchema.getMyExpiredSessionsStatement(connection, _context, upperBound))
            {
                try (ResultSet result = statement.executeQuery())
                {
                    while (result.next())
                    {
                        String sessionId = result.getString(_sessionTableSchema.getIdColumn());
                        long exp = result.getLong(_sessionTableSchema.getExpiryTimeColumn());
                        expiredSessionKeys.add(sessionId);
                        if (LOG.isDebugEnabled())
                            LOG.debug("{} - Found expired sessionId={}, in context={}, expiry={}",
                                _context.getWorkerName(), sessionId, _context.getCanonicalContextPath(), exp);
                    }
                }
            }

            Set notExpiredInDB = new HashSet<>();
            for (String k : candidates)
            {
                //there are some keys that the sessioncache thought had expired, but were not
                //found in our query either because it is no longer in the db, or its
                //expiry time was updated
                if (!expiredSessionKeys.contains(k))
                    notExpiredInDB.add(k);
            }

            //Check the candidates that were not reported as expired in the db: they
            //either do not exist, or they weren't expired (which means some other node
            //must be managing it)
            if (!notExpiredInDB.isEmpty())
            {
                try (PreparedStatement checkSessionExists = _sessionTableSchema.getCheckSessionExistsStatement(connection, _context))
                {
                    for (String k : notExpiredInDB)
                    {
                        checkSessionExists.setString(1, k);
                        try (ResultSet result = checkSessionExists.executeQuery())
                        {
                            if (!result.next())
                            {
                                //session doesn't exist any more, can be expired
                                expiredSessionKeys.add(k);
                            }
                            else
                            {
                                if (LOG.isDebugEnabled())
                                    LOG.debug("{} Session {} expiry fresher in db than cache, another node must be managing it", _context.getWorkerName(), k);
                            }
                        }
                        catch (Exception e)
                        {
                            LOG.warn("{} Problem checking if potentially expired session {} exists in db", _context.getWorkerName(), k, e);
                        }
                    }
                }
            }

            return expiredSessionKeys;
        }
        catch (Exception e)
        {
            LOG.warn("Unable to get expired sessions", e);
            return expiredSessionKeys; //return whatever we got
        }
    }

    @Override
    public Set doGetExpired(long timeLimit)
    {
        Set expired = new HashSet<>();
        
        //Get sessions for my context but managed by any node that expired at or before the timeLimit   
        try (Connection connection = _dbAdaptor.getConnection())
        {
            connection.setAutoCommit(true);
            try (PreparedStatement selectExpiredSessions = _sessionTableSchema.getExpiredSessionsStatement(connection, _context.getCanonicalContextPath(),
                                                                                                           _context.getVhost(), timeLimit))
            {
                if (LOG.isDebugEnabled()) 
                    LOG.debug("{}- Searching for sessions for context {} expired before {}", _context.getWorkerName(), _context.getCanonicalContextPath(), timeLimit);

                try (ResultSet result = selectExpiredSessions.executeQuery())
                {
                    while (result.next())
                    {
                        String sessionId = result.getString(_sessionTableSchema.getIdColumn());
                        long exp = result.getLong(_sessionTableSchema.getExpiryTimeColumn());
                        expired.add(sessionId);
                        if (LOG.isDebugEnabled())
                            LOG.debug("{}- Found expired sessionId={} for context={} expiry={}",
                                _context.getWorkerName(), sessionId, _context.getCanonicalContextPath(), exp);
                    }
                }
            }
            return expired;
        }
        catch (Exception e)
        {
            LOG.warn("Error finding sessions expired before {}", timeLimit, e);
            return expired; //return whatever we got
        } 
    }

    @Override
    public void doCleanOrphans(long time)
    {
        //Harshly delete sessions for any node and context that expired at or before the timeLimit
        try (Connection connection = _dbAdaptor.getConnection();
             PreparedStatement statement = _sessionTableSchema.getCleanOrphansStatement(connection, time))
        {
            connection.setAutoCommit(true);
            int rows = statement.executeUpdate();
            if (LOG.isDebugEnabled())
                LOG.debug("Deleted {} orphaned sessions", rows);
        }
        catch (Exception e)
        {
            LOG.warn("Error cleaning orphan sessions", e);
        } 
    }

    public void setDatabaseAdaptor(DatabaseAdaptor dbAdaptor)
    {
        checkStarted();
        updateBean(_dbAdaptor, dbAdaptor);
        _dbAdaptor = dbAdaptor;
    }

    public void setSessionTableSchema(SessionTableSchema schema)
    {
        checkStarted();
        updateBean(_sessionTableSchema, schema);
        _sessionTableSchema = schema;
        _schemaProvided = true;
    }

    @Override
    @ManagedAttribute(value = "does this store serialize sessions", readonly = true)
    public boolean isPassivating()
    {
        return true;
    }

    @Override
    public boolean doExists(String id)
        throws Exception
    {
        try (Connection connection = _dbAdaptor.getConnection())
        {
            connection.setAutoCommit(true);

            //non-expired session exists?
            try (PreparedStatement checkSessionExists = _sessionTableSchema.getCheckSessionExistsStatement(connection, _context))
            {
                checkSessionExists.setString(1, id);
                try (ResultSet result = checkSessionExists.executeQuery())
                {
                    if (!result.next())
                    {
                        return false; //no such session
                    }
                    else
                    {
                        long expiry = result.getLong(_sessionTableSchema.getExpiryTimeColumn());
                        if (expiry <= 0) //never expires
                            return true;
                        else
                            return (expiry > System.currentTimeMillis()); //hasn't already expired
                    }
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy