Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  //
  //  ========================================================================
  //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
  //  ------------------------------------------------------------------------
  //  All rights reserved. This program and the accompanying materials
  //  are made available under the terms of the Eclipse Public License v1.0
  //  and Apache License v2.0 which accompanies this distribution.
  //
  //      The Eclipse Public License is available at
 //      http://www.eclipse.org/legal/epl-v10.html
 //
 //      The Apache License v2.0 is available at
 //      http://www.opensource.org/licenses/apache2.0.php
 //
 //  You may elect to redistribute this code under either of these licenses.
 //  ========================================================================
 //
 
 package org.eclipse.jetty.server.session;
 
 import java.sql.Blob;
 import java.util.List;
 
 
JDBCSessionIdManager SessionIdManager implementation that uses a database to store in-use session ids, to support distributed sessions.
 
 {    
     final static Logger LOG = .;
     
     protected final HashSet<String_sessionIds = new HashSet<String>();
     protected Server _server;
     protected Driver _driver;
     protected String _driverClassName;
     protected String _connectionUrl;
     protected DataSource _datasource;
     protected String _jndiName;
     protected String _sessionIdTable = "JettySessionIds";
     protected String _sessionTable = "JettySessions";
     protected String _sessionTableRowId = "rowId";
     
     protected Timer _timer//scavenge timer
     protected TimerTask _task//scavenge task
     protected long _lastScavengeTime;
     protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins
     protected String _blobType//if not set, is deduced from the type of the database at runtime
     protected String _longType//if not set, is deduced from the type of the database at runtime
     
     protected String _createSessionIdTable;
     protected String _createSessionTable;
                                             
     protected String _deleteOldExpiredSessions;
 
     protected String _insertId;
     protected String _deleteId;
     protected String _queryId;
     
     protected  String _insertSession;
     protected  String _deleteSession;
     protected  String _updateSession;
     protected  String _updateSessionNode;
     protected  String _updateSessionAccessTime;
     
    protected DatabaseAdaptor _dbAdaptor;
    private String _selectExpiredSessions;

    
    
DatabaseAdaptor Handles differences between databases. Postgres uses the getBytes and setBinaryStream methods to access a "bytea" datatype, which can be up to 1Gb of binary data. MySQL is happy to use the "blob" type and getBlob() methods instead. TODO if the differences become more major it would be worthwhile refactoring this class.
    public class DatabaseAdaptor 
    {
        String _dbName;
        boolean _isLower;
        boolean _isUpper;
       
        
        
        public DatabaseAdaptor (DatabaseMetaData dbMeta)
        throws SQLException
        {
             = dbMeta.getDatabaseProductName().toLowerCase(.); 
            .debug ("Using database {}",);
             = dbMeta.storesLowerCaseIdentifiers();
             = dbMeta.storesUpperCaseIdentifiers();            
        }
        
        
Convert a camel case identifier into either upper or lower depending on the way the db stores identifiers.

Parameters:
identifier
Returns:
the converted identifier
        public String convertIdentifier (String identifier)
        {
            if ()
                return identifier.toLowerCase(.);
            if ()
                return identifier.toUpperCase(.);
            
            return identifier;
        }
        
        public String getDBName ()
        {
            return ;
        }
        
        public String getBlobType ()
        {
            if ( != null)
                return ;
            
            if (.startsWith("postgres"))
                return "bytea";
            
            return "blob";
        }
        
        public String getLongType ()
        {
            if ( != null)
                return ;
            
            if (.startsWith("oracle"))
                return "number(20)";
            
            return "bigint";
        }
        
        public InputStream getBlobInputStream (ResultSet resultString columnName)
        throws SQLException
        {
            if (.startsWith("postgres"))
            {
                byte[] bytes = result.getBytes(columnName);
                return new ByteArrayInputStream(bytes);
            }
            
            Blob blob = result.getBlob(columnName);
            return blob.getBinaryStream();
        }
        
        
rowId is a reserved word for Oracle, so change the name of this column

Returns:
        public String getRowIdColumnName ()
        {
            if ( != null && .startsWith("oracle"))
                return "srowId";
            
            return "rowId";
        }
        
        
        public boolean isEmptyStringNull ()
        {
            return (.startsWith("oracle"));
        }
        
        public PreparedStatement getLoadStatement (Connection connectionString rowIdString contextPathString virtualHosts
        throws SQLException
        {
            if (contextPath == null || "".equals(contextPath))
            {
                if (isEmptyStringNull())
                {
                    PreparedStatement statement = connection.prepareStatement("select * from "++
                    " where sessionId = ? and contextPath is null and virtualHost = ?");
                    statement.setString(1, rowId);
                    statement.setString(2, virtualHosts);
                    return statement;
                }
            }
           
            PreparedStatement statement = connection.prepareStatement("select * from "++
            " where sessionId = ? and contextPath = ? and virtualHost = ?");
            statement.setString(1, rowId);
            statement.setString(2, contextPath);
            statement.setString(3, virtualHosts);
            return statement;
        }
    }
    
    
    
    public JDBCSessionIdManager(Server server)
    {
        super();
        =server;
    }
    
    public JDBCSessionIdManager(Server serverRandom random)
    {
       super(random);
       =server;
    }

    
Configure jdbc connection information via a jdbc Driver

Parameters:
driverClassName
connectionUrl
    public void setDriverInfo (String driverClassNameString connectionUrl)
    {
        =driverClassName;
        =connectionUrl;
    }
    
    
Configure jdbc connection information via a jdbc Driver

Parameters:
driverClass
connectionUrl
    public void setDriverInfo (Driver driverClassString connectionUrl)
    {
        =driverClass;
        =connectionUrl;
    }
    
    
    public void setDatasource (DataSource ds)
    {
         = ds;
    }
    
    public DataSource getDataSource ()
    {
        return ;
    }
    
    public String getDriverClassName()
    {
        return ;
    }
    
    public String getConnectionUrl ()
    {
        return ;
    }
    
    public void setDatasourceName (String jndi)
    {
        =jndi;
    }
    
    public String getDatasourceName ()
    {
        return ;
    }
   
    public void setBlobType (String name)
    {
         = name;
    }
    
    public String getBlobType ()
    {
        return ;
    }
    
    
    
    public String getLongType()
    {
        return ;
    }
    public void setLongType(String longType)
    {
        this. = longType;
    }
    public void setScavengeInterval (long sec)
    {
        if (sec<=0)
            sec=60;
        long old_period=;
        long period=sec*1000L;
      
        =period;
        
        //add a bit of variability into the scavenge time so that not all
        //nodes with the same scavenge time sync up
        long tenPercent = /10;
        if ((System.currentTimeMillis()%2) == 0)
             += tenPercent;
        
        if (.isDebugEnabled()) 
            .debug("Scavenging every "++" ms");
        if (!=null && (period!=old_period || ==null))
        {
            synchronized (this)
            {
                if (!=null)
                    .cancel();
                 = new TimerTask()
                {
                    @Override
                    public void run()
                    {
                        scavenge();
                    }   
                };
            }
        }  
    }
    
    public long getScavengeInterval ()
    {
        return /1000;
    }
    
    
    public void addSession(HttpSession session)
    {
        if (session == null)
            return;
        
        synchronized ()
        {
            String id = ((JDBCSessionManager.Session)session).getClusterId();            
            try
            {
                insert(id);
                .add(id);
            }
            catch (Exception e)
            {
                .warn("Problem storing session id="+ide);
            }
        }
    }
    
    public void removeSession(HttpSession session)
    {
        if (session == null)
            return;
        
    }
    
    
    
    public void removeSession (String id)
    {
        if (id == null)
            return;
        
        synchronized ()
        {  
            if (.isDebugEnabled())
                .debug("Removing session id="+id);
            try
            {               
                .remove(id);
                delete(id);
            }
            catch (Exception e)
            {
                .warn("Problem removing session id="+ide);
            }
        }
        
    }
    

    
Get the session id without any node identifier suffix.

    public String getClusterId(String nodeId)
    {
        int dot=nodeId.lastIndexOf('.');
        return (dot>0)?nodeId.substring(0,dot):nodeId;
    }
    

    
    public String getNodeId(String clusterIdHttpServletRequest request)
    {
        if (!=null)
            return clusterId+'.'+;
        return clusterId;
    }
    public boolean idInUse(String id)
    {
        if (id == null)
            return false;
        
        String clusterId = getClusterId(id);
        boolean inUse = false;
        synchronized ()
        {
            inUse = .contains(clusterId);
        }
        
        
        if (inUse)
            return true//optimisation - if this session is one we've been managing, we can check locally
        //otherwise, we need to go to the database to check
        try
        {
            return exists(clusterId);
        }
        catch (Exception e)
        {
            .warn("Problem checking inUse for id="+clusterIde);
            return false;
        }
    }

    
Invalidate the session matching the id on all contexts.

    public void invalidateAll(String id)
    {            
        //take the id out of the list of known sessionids for this node
        removeSession(id);
        
        synchronized ()
        {
            //tell all contexts that may have a session object with this id to
            //get rid of them
            Handler[] contexts = .getChildHandlersByClass(ContextHandler.class);
            for (int i=0; contexts!=null && i<contexts.lengthi++)
            {
                SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
                if (sessionHandler != null
                {
                    SessionManager manager = sessionHandler.getSessionManager();
                    if (manager != null && manager instanceof JDBCSessionManager)
                    {
                        ((JDBCSessionManager)manager).invalidateSession(id);
                    }
                }
            }
        }
    }


    
Start up the id manager. Makes necessary database tables and starts a Session scavenger thread.
    @Override
    public void doStart()
    {
        try
        {            
            initializeDatabase();
            prepareTables();   
            cleanExpiredSessions();
            super.doStart();
            if (.isDebugEnabled()) 
                .debug("Scavenging interval = "+getScavengeInterval()+" sec");
            =new Timer("JDBCSessionScavenger"true);
            setScavengeInterval(getScavengeInterval());
        }
        catch (Exception e)
        {
            .warn("Problem initialising JettySessionIds table"e);
        }
    }
    
    
Stop the scavenger.
    @Override
    public void doStop () 
    throws Exception
    {
        synchronized(this)
        {
            if (!=null)
                .cancel();
            if (!=null)
                .cancel();
            =null;
        }
        .clear();
        super.doStop();
    }
  
    
Get a connection from the driver or datasource.

Returns:
the connection for the datasource
Throws:
java.sql.SQLException
    protected Connection getConnection ()
    throws SQLException
    {
        if ( != null)
            return .getConnection();
        else
            return DriverManager.getConnection();
    }
    
    
   
    
    
    
Set up the tables in the database

    private void prepareTables()
    throws SQLException
    {
         = "create table "++" (id varchar(120), primary key(id))";
         = "select * from "++" where expiryTime >= ? and expiryTime <= ?";
         = "select * from "++" where expiryTime >0 and expiryTime <= ?";
         = "delete from "++" where expiryTime >0 and expiryTime <= ?";
         = "insert into "++" (id)  values (?)";
         = "delete from "++" where id = ?";
         = "select * from "++" where id = ?";
        Connection connection = null;
        try
        {
            //make the id table
            connection = getConnection();
            connection.setAutoCommit(true);
            DatabaseMetaData metaData = connection.getMetaData();
             = new DatabaseAdaptor(metaData);
             = .getRowIdColumnName();
            //checking for table existence is case-sensitive, but table creation is not
            String tableName = .convertIdentifier();
            ResultSet result = metaData.getTables(nullnulltableNamenull);
            if (!result.next())
            {
                //table does not exist, so create it
                connection.createStatement().executeUpdate();
            }
            
            //make the session table if necessary
            tableName = .convertIdentifier();   
            result = metaData.getTables(nullnulltableNamenull);
            if (!result.next())
            {
                //table does not exist, so create it
                String blobType = .getBlobType();
                String longType = .getLongType();
                 = "create table "++" ("++" varchar(120), sessionId varchar(120), "+
                                           " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime "+longType+", "+
                                           " lastAccessTime "+longType+", createTime "+longType+", cookieTime "+longType+", "+
                                           " lastSavedTime "+longType+", expiryTime "+longType+", map "+blobType+", primary key("++"))";
                connection.createStatement().executeUpdate();
            }
            
            //make some indexes on the JettySessions table
            String index1 = "idx_"++"_expiry";
            String index2 = "idx_"++"_session";
            
            result = metaData.getIndexInfo(nullnulltableNamefalsefalse);
            boolean index1Exists = false;
            boolean index2Exists = false;
            while (result.next())
            {
                String idxName = result.getString("INDEX_NAME");
                if (index1.equalsIgnoreCase(idxName))
                    index1Exists = true;
                else if (index2.equalsIgnoreCase(idxName))
                    index2Exists = true;
            }
            if (!(index1Exists && index2Exists))
            {
                Statement statement = connection.createStatement();
                if (!index1Exists)
                    statement.executeUpdate("create index "+index1+" on "++" (expiryTime)");
                if (!index2Exists)
                    statement.executeUpdate("create index "+index2+" on "++" (sessionId, contextPath)");
            }
            //set up some strings representing the statements for session manipulation
             = "insert into "++
            " ("++", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+
            " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
             = "delete from "++
            " where "++" = ?";
            
             = "update "++
            " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where "++" = ?";
             = "update "++
            " set lastNode = ? where "++" = ?";
             = "update "++
            " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "++" = ?";
            
        }
        finally
        {
            if (connection != null)
                connection.close();
        }
    }
    
    
Insert a new used session id into the table.

Parameters:
id
Throws:
java.sql.SQLException
    private void insert (String id)
    throws SQLException 
    {
        Connection connection = null;
        try
        {
            connection = getConnection();
            connection.setAutoCommit(true);            
            PreparedStatement query = connection.prepareStatement();
            query.setString(1, id);
            ResultSet result = query.executeQuery();
            //only insert the id if it isn't in the db already 
            if (!result.next())
            {
                PreparedStatement statement = connection.prepareStatement();
                statement.setString(1, id);
                statement.executeUpdate();
            }
        }
        finally
        {
            if (connection != null)
                connection.close();
        }
    }
    
    
Remove a session id from the table.

Parameters:
id
Throws:
java.sql.SQLException
    private void delete (String id)
    throws SQLException
    {
        Connection connection = null;
        try
        {
            connection = getConnection();
            connection.setAutoCommit(true);
            PreparedStatement statement = connection.prepareStatement();
            statement.setString(1, id);
            statement.executeUpdate();
        }
        finally
        {
            if (connection != null)
                connection.close();
        }
    }
    
    
    
Check if a session id exists.

Parameters:
id
Returns:
Throws:
java.sql.SQLException
    private boolean exists (String id)
    throws SQLException
    {
        Connection connection = null;
        try
        {
            connection = getConnection();
            connection.setAutoCommit(true);
            PreparedStatement statement = connection.prepareStatement();
            statement.setString(1, id);
            ResultSet result = statement.executeQuery();
            return result.next();
        }
        finally
        {
            if (connection != null)
                connection.close();
        }
    }
    
    
Look for sessions in the database that have expired. We do this in the SessionIdManager and not the SessionManager so that we only have 1 scavenger, otherwise if there are n SessionManagers there would be n scavengers, all contending for the database. We look first for sessions that expired in the previous interval, then for sessions that expired previously - these are old sessions that no node is managing any more and have become stuck in the database.
    private void scavenge ()
    {
        Connection connection = null;
        List<StringexpiredSessionIds = new ArrayList<String>();
        try
        {            
            if (.isDebugEnabled()) 
                .debug("Scavenge sweep started at "+System.currentTimeMillis());
            if ( > 0)
            {
                connection = getConnection();
                connection.setAutoCommit(true);
                //"select sessionId from JettySessions where expiryTime > (lastScavengeTime - scanInterval) and expiryTime < lastScavengeTime";
                PreparedStatement statement = connection.prepareStatement();
                long lowerBound = ( - );
                long upperBound = ;
                if (.isDebugEnabled()) 
                    .debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound);
                
                statement.setLong(1, lowerBound);
                statement.setLong(2, upperBound);
                ResultSet result = statement.executeQuery();
                while (result.next())
                {
                    String sessionId = result.getString("sessionId");
                    expiredSessionIds.add(sessionId);
                    if (.isDebugEnabled()) .debug (" Found expired sessionId="+sessionId); 
                }
                //tell the SessionManagers to expire any sessions with a matching sessionId in memory
                Handler[] contexts = .getChildHandlersByClass(ContextHandler.class);
                for (int i=0; contexts!=null && i<contexts.lengthi++)
                {
                    SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
                    if (sessionHandler != null
                    { 
                        SessionManager manager = sessionHandler.getSessionManager();
                        if (manager != null && manager instanceof JDBCSessionManager)
                        {
                            ((JDBCSessionManager)manager).expire(expiredSessionIds);
                        }
                    }
                }
                //find all sessions that have expired at least a couple of scanIntervals ago and just delete them
                upperBound =  - (2 * );
                if (upperBound > 0)
                {
                    if (.isDebugEnabled()) .debug("Deleting old expired sessions expired before "+upperBound);
                    statement = connection.prepareStatement();
                    statement.setLong(1, upperBound);
                    int rows = statement.executeUpdate();
                    if (.isDebugEnabled()) .debug("Deleted "+rows+" rows of old sessions expired before "+upperBound);
                }
            }
        }
        catch (Exception e)
        {
            if (isRunning())    
                .warn("Problem selecting expired sessions"e);
            else
                .ignore(e);
        }
        finally
        {           
            =System.currentTimeMillis();
            if (.isDebugEnabled()) .debug("Scavenge sweep ended at "+);
            if (connection != null)
            {
                try
                {
                connection.close();
                }
                catch (SQLException e)
                {
                    .warn(e);
                }
            }
        }
    }
    
    
Get rid of sessions and sessionids from sessions that have already expired

    private void cleanExpiredSessions ()
    throws Exception
    {
        Connection connection = null;
        List<StringexpiredSessionIds = new ArrayList<String>();
        try
        {     
            connection = getConnection();
            connection.setAutoCommit(false);
            PreparedStatement statement = connection.prepareStatement();
            long now = System.currentTimeMillis();
            if (.isDebugEnabled()) .debug ("Searching for sessions expired before {}"now);
            statement.setLong(1, now);
            ResultSet result = statement.executeQuery();
            while (result.next())
            {
                String sessionId = result.getString("sessionId");
                expiredSessionIds.add(sessionId);
                if (.isDebugEnabled()) .debug ("Found expired sessionId={}"sessionId); 
            }
            
            Statement sessionsTableStatement = null;
            Statement sessionIdsTableStatement = null;
            if (!expiredSessionIds.isEmpty())
            {
                sessionsTableStatement = connection.createStatement();
                sessionsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "++" where sessionId in "expiredSessionIds));
                sessionIdsTableStatement = connection.createStatement();
                sessionIdsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "++" where id in "expiredSessionIds));
            }
            connection.commit();
            synchronized ()
            {
                .removeAll(expiredSessionIds); //in case they were in our local cache of session ids
            }
        }
        catch (Exception e)
        {
            if (connection != null)
                connection.rollback();
            throw e;
        }
        finally
        {
            try
            {
                if (connection != null)
                    connection.close();
            }
            catch (SQLException e)
            {
                .warn(e);
            }
        }
    }
    
    
    

Parameters:
sql
connection
expiredSessionIds
Throws:
java.lang.Exception
    private String createCleanExpiredSessionsSql (String sql,Collection<StringexpiredSessionIds)
    throws Exception
    {
        StringBuffer buff = new StringBuffer();
        buff.append(sql);
        buff.append("(");
        Iterator<Stringitor = expiredSessionIds.iterator();
        while (itor.hasNext())
        {
            buff.append("'"+(itor.next())+"'");
            if (itor.hasNext())
                buff.append(",");
        }
        buff.append(")");
        
        if (.isDebugEnabled()) .debug("Cleaning expired sessions with: {}"buff);
        return buff.toString();
    }
    
    private void initializeDatabase ()
    throws Exception
    {
        if ( != null)
            return//already set up
        
        if (!=null)
        {
            InitialContext ic = new InitialContext();
             = (DataSource)ic.lookup();
        }
        else if (  != null &&  != null )
        {
            DriverManager.registerDriver();
        }
        else if ( != null &&  != null)
        {
            Class.forName();
        }
        else
            throw new IllegalStateException("No database configured for sessions");
    }
    
   
New to GrepCode? Check out our FAQ X