org.eclipse.jetty.server.session.JDBCSessionManager Maven / Gradle / Ivy
The newest version!
//
// ========================================================================
// Copyright (c) 1995-2012 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* JDBCSessionManager
*
* SessionManager that persists sessions to a database to enable clustering.
*
* Session data is persisted to the JettySessions table:
*
* rowId (unique in cluster: webapp name/path + virtualhost + sessionId)
* contextPath (of the context owning the session)
* sessionId (unique in a context)
* lastNode (name of node last handled session)
* accessTime (time in milliseconds session was accessed)
* lastAccessTime (previous time in milliseconds session was accessed)
* createTime (time in milliseconds session created)
* cookieTime (time in milliseconds session cookie created)
* lastSavedTime (last time in milliseconds session access times were saved)
* expiryTime (time in milliseconds that the session is due to expire)
* map (attribute map)
*
* As an optimization, to prevent thrashing the database, we do not persist
* the accessTime and lastAccessTime every time the session is accessed. Rather,
* we write it out every so often. The frequency is controlled by the saveIntervalSec
* field.
*/
public class JDBCSessionManager extends AbstractSessionManager
{
private static final Logger LOG = Log.getLogger(JDBCSessionManager.class);
private ConcurrentHashMap _sessions;
protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
/**
* SessionData
*
* Persistable data about a session.
*/
public class SessionData
{
private String _id;
private String _rowId;
private long _accessed;
private long _lastAccessed;
private long _maxIdleMs=-1;
private long _cookieSet;
private long _created;
private Map _attributes;
private String _lastNode;
private String _canonicalContext;
private long _lastSaved;
private long _expiryTime;
private String _virtualHost;
public SessionData (String sessionId)
{
_id=sessionId;
_created=System.currentTimeMillis();
_accessed = _created;
_attributes = new HashMap();
_lastNode = getSessionIdManager().getWorkerName();
}
public SessionData (String sessionId,Map attributes)
{
_id=sessionId;
_created=System.currentTimeMillis();
_accessed = _created;
_attributes = attributes;
_lastNode = getSessionIdManager().getWorkerName();
}
public synchronized String getId ()
{
return _id;
}
public synchronized void setId (String id)
{
_id = id;
}
public synchronized long getCreated ()
{
return _created;
}
protected synchronized void setCreated (long ms)
{
_created = ms;
}
public synchronized long getAccessed ()
{
return _accessed;
}
protected synchronized void setAccessed (long ms)
{
_accessed = ms;
}
public synchronized void setMaxIdleMs (long ms)
{
_maxIdleMs = ms;
}
public synchronized long getMaxIdleMs()
{
return _maxIdleMs;
}
public synchronized void setLastAccessed (long ms)
{
_lastAccessed = ms;
}
public synchronized long getLastAccessed()
{
return _lastAccessed;
}
public void setCookieSet (long ms)
{
_cookieSet = ms;
}
public synchronized long getCookieSet ()
{
return _cookieSet;
}
public synchronized void setRowId (String rowId)
{
_rowId=rowId;
}
protected synchronized String getRowId()
{
return _rowId;
}
protected synchronized Map getAttributeMap ()
{
return _attributes;
}
protected synchronized void setAttributeMap (Map map)
{
_attributes = map;
}
public synchronized void setLastNode (String node)
{
_lastNode=node;
}
public synchronized String getLastNode ()
{
return _lastNode;
}
public synchronized void setCanonicalContext(String str)
{
_canonicalContext=str;
}
public synchronized String getCanonicalContext ()
{
return _canonicalContext;
}
public synchronized long getLastSaved ()
{
return _lastSaved;
}
public synchronized void setLastSaved (long time)
{
_lastSaved=time;
}
public synchronized void setExpiryTime (long time)
{
_expiryTime=time;
}
public synchronized long getExpiryTime ()
{
return _expiryTime;
}
public synchronized void setVirtualHost (String vhost)
{
_virtualHost=vhost;
}
public synchronized String getVirtualHost ()
{
return _virtualHost;
}
@Override
public String toString ()
{
return "Session rowId="+_rowId+",id="+_id+",lastNode="+_lastNode+
",created="+_created+",accessed="+_accessed+
",lastAccessed="+_lastAccessed+",cookieSet="+_cookieSet+
"lastSaved="+_lastSaved;
}
}
/**
* Session
*
* Session instance in memory of this node.
*/
public class Session extends AbstractSession
{
private static final long serialVersionUID = 5208464051134226143L;
private final SessionData _data;
private boolean _dirty=false;
/**
* Session from a request.
*
* @param request
*/
protected Session (HttpServletRequest request)
{
super(JDBCSessionManager.this,request);
_data = new SessionData(getClusterId(),getAttributeMap());
if (_dftMaxIdleSecs>0)
_data.setMaxIdleMs(_dftMaxIdleSecs*1000L);
_data.setCanonicalContext(canonicalize(_context.getContextPath()));
_data.setVirtualHost(getVirtualHost(_context));
int maxInterval=getMaxInactiveInterval();
_data.setExpiryTime(maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
}
/**
* Session restored in database.
* @param data
*/
protected Session (long accessed, SessionData data)
{
super(JDBCSessionManager.this,data.getCreated(), accessed, data.getId());
_data=data;
if (_dftMaxIdleSecs>0)
_data.setMaxIdleMs(_dftMaxIdleSecs*1000L);
addAttributes(_data.getAttributeMap());
_data.setAttributeMap(getAttributeMap());
}
@Override
public void setAttribute (String name, Object value)
{
super.setAttribute(name, value);
_dirty=true;
}
@Override
public void removeAttribute (String name)
{
super.removeAttribute(name);
_dirty=true;
}
@Override
protected void setClusterId(String clusterId)
{
super.setClusterId(clusterId);
_data.setId(clusterId);
_dirty = true;
}
@Override
protected void setNodeId(String nodeId)
{
_data.setLastNode(nodeId);
super.setNodeId(nodeId);
_dirty = true;
}
protected void save() throws Exception
{
try
{
updateSession(_data);
}
finally
{
_dirty = false;
}
}
@Override
protected void cookieSet()
{
_data.setCookieSet(_data.getAccessed());
}
/**
* Entry to session.
* Called by SessionHandler on inbound request and the session already exists in this node's memory.
*
* @see org.eclipse.jetty.server.session.AbstractSession#access(long)
*/
@Override
protected boolean access(long time)
{
if (super.access(time))
{
_data.setLastAccessed(_data.getAccessed());
_data.setAccessed(time);
int maxInterval=getMaxInactiveInterval();
_data.setExpiryTime(maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
return true;
}
return false;
}
/**
* Exit from session
* @see org.eclipse.jetty.server.session.AbstractSession#complete()
*/
@Override
protected void complete()
{
super.complete();
try
{
if (_dirty)
{
//The session attributes have changed, write to the db, ensuring
//http passivation/activation listeners called
willPassivate();
updateSession(_data);
didActivate();
}
else if ((_data._accessed - _data._lastSaved) >= (getSaveInterval() * 1000L))
{
updateSessionAccessTime(_data);
}
}
catch (Exception e)
{
LOG.warn("Problem persisting changed session data id="+getId(), e);
}
finally
{
_dirty=false;
}
}
@Override
protected void timeout() throws IllegalStateException
{
if (LOG.isDebugEnabled())
LOG.debug("Timing out session id="+getClusterId());
super.timeout();
}
}
/**
* ClassLoadingObjectInputStream
*
*
*/
protected class ClassLoadingObjectInputStream extends ObjectInputStream
{
public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
{
super(in);
}
public ClassLoadingObjectInputStream () throws IOException
{
super();
}
@Override
public Class> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
{
try
{
return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
}
catch (ClassNotFoundException e)
{
return super.resolveClass(cl);
}
}
}
/**
* Set the time in seconds which is the interval between
* saving the session access time to the database.
*
* This is an optimization that prevents the database from
* being overloaded when a session is accessed very frequently.
*
* On session exit, if the session attributes have NOT changed,
* the time at which we last saved the accessed
* time is compared to the current accessed time. If the interval
* is at least saveIntervalSecs, then the access time will be
* persisted to the database.
*
* If any session attribute does change, then the attributes and
* the accessed time are persisted.
*
* @param sec
*/
public void setSaveInterval (long sec)
{
_saveIntervalSec=sec;
}
public long getSaveInterval ()
{
return _saveIntervalSec;
}
/**
* A method that can be implemented in subclasses to support
* distributed caching of sessions. This method will be
* called whenever the session is written to the database
* because the session data has changed.
*
* This could be used eg with a JMS backplane to notify nodes
* that the session has changed and to delete the session from
* the node's cache, and re-read it from the database.
* @param session
*/
public void cacheInvalidate (Session session)
{
}
/**
* A session has been requested by it's id on this node.
*
* Load the session by id AND context path from the database.
* Multiple contexts may share the same session id (due to dispatching)
* but they CANNOT share the same contents.
*
* Check if last node id is my node id, if so, then the session we have
* in memory cannot be stale. If another node used the session last, then
* we need to refresh from the db.
*
* NOTE: this method will go to the database, so if you only want to check
* for the existence of a Session in memory, use _sessions.get(id) instead.
*
* @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
*/
@Override
public Session getSession(String idInCluster)
{
Session session = (Session)_sessions.get(idInCluster);
synchronized (this)
{
try
{
//check if we need to reload the session -
//as an optimization, don't reload on every access
//to reduce the load on the database. This introduces a window of
//possibility that the node may decide that the session is local to it,
//when the session has actually been live on another node, and then
//re-migrated to this node. This should be an extremely rare occurrence,
//as load-balancers are generally well-behaved and consistently send
//sessions to the same node, changing only iff that node fails.
SessionData data = null;
long now = System.currentTimeMillis();
if (LOG.isDebugEnabled())
{
if (session==null)
LOG.debug("getSession("+idInCluster+"): not in session map,"+
" now="+now+
" lastSaved="+(session==null?0:session._data._lastSaved)+
" interval="+(_saveIntervalSec * 1000L));
else
LOG.debug("getSession("+idInCluster+"): in session map, "+
" now="+now+
" lastSaved="+(session==null?0:session._data._lastSaved)+
" interval="+(_saveIntervalSec * 1000L)+
" lastNode="+session._data.getLastNode()+
" thisNode="+getSessionIdManager().getWorkerName()+
" difference="+(now - session._data._lastSaved));
}
if (session==null || ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000L)))
{
LOG.debug("getSession("+idInCluster+"): no session in session map or stale session. Reloading session data from db.");
data = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
}
else if ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000L))
{
LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
data = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
}
else
{
LOG.debug("getSession("+idInCluster+"): session in session map");
data = session._data;
}
if (data != null)
{
if (!data.getLastNode().equals(getSessionIdManager().getWorkerName()) || session==null)
{
//if the session has no expiry, or it is not already expired
if (data._expiryTime <= 0 || data._expiryTime > now)
{
if (LOG.isDebugEnabled()) LOG.debug("getSession("+idInCluster+"): lastNode="+data.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
data.setLastNode(getSessionIdManager().getWorkerName());
//session last used on a different node, or we don't have it in memory
session = new Session(now,data);
_sessions.put(idInCluster, session);
session.didActivate();
//TODO is this the best way to do this? Or do this on the way out using
//the _dirty flag?
updateSessionNode(data);
}
else
{
LOG.debug("getSession ({}): Session has expired", idInCluster);
}
}
else
LOG.debug("getSession({}): Session not stale {}", idInCluster,session._data);
//session in db shares same id, but is not for this context
}
else
{
//No session in db with matching id and context path.
session=null;
LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
}
return session;
}
catch (Exception e)
{
LOG.warn("Unable to load session from database", e);
return null;
}
}
}
/**
* Get the number of sessions.
*
* @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions()
*/
@Override
public int getSessions()
{
int size = 0;
synchronized (this)
{
size = _sessions.size();
}
return size;
}
/**
* Start the session manager.
*
* @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
*/
@Override
public void doStart() throws Exception
{
if (_sessionIdManager==null)
throw new IllegalStateException("No session id manager defined");
_jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager;
_sessions = new ConcurrentHashMap();
super.doStart();
}
/**
* Stop the session manager.
*
* @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
*/
@Override
public void doStop() throws Exception
{
_sessions.clear();
_sessions = null;
super.doStop();
}
@Override
protected void invalidateSessions()
{
//Do nothing - we don't want to remove and
//invalidate all the sessions because this
//method is called from doStop(), and just
//because this context is stopping does not
//mean that we should remove the session from
//any other nodes
}
/**
*
* @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
public void renewSessionId (String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
{
Session session = null;
synchronized (this)
{
try
{
session = (Session)_sessions.remove(oldClusterId);
if (session != null)
{
session.setClusterId(newClusterId); //update ids
session.setNodeId(newNodeId);
_sessions.put(newClusterId, session); //put it into list in memory
session.save(); //update database
}
}
catch (Exception e)
{
LOG.warn(e);
}
}
}
/**
* Invalidate a session.
*
* @param idInCluster
*/
protected void invalidateSession (String idInCluster)
{
Session session = null;
synchronized (this)
{
session = (Session)_sessions.get(idInCluster);
}
if (session != null)
{
session.invalidate();
}
}
/**
* Delete an existing session, both from the in-memory map and
* the database.
*
* @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
*/
@Override
protected boolean removeSession(String idInCluster)
{
synchronized (this)
{
Session session = (Session)_sessions.remove(idInCluster);
try
{
if (session != null)
deleteSession(session._data);
}
catch (Exception e)
{
LOG.warn("Problem deleting session id="+idInCluster, e);
}
return session!=null;
}
}
/**
* Add a newly created session to our in-memory list for this node and persist it.
*
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
*/
@Override
protected void addSession(AbstractSession session)
{
if (session==null)
return;
synchronized (this)
{
_sessions.put(session.getClusterId(), session);
}
//TODO or delay the store until exit out of session? If we crash before we store it
//then session data will be lost.
try
{
session.willPassivate();
storeSession(((JDBCSessionManager.Session)session)._data);
session.didActivate();
}
catch (Exception e)
{
LOG.warn("Unable to store new session id="+session.getId() , e);
}
}
/**
* Make a new Session.
*
* @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
*/
@Override
protected AbstractSession newSession(HttpServletRequest request)
{
return new Session(request);
}
/* ------------------------------------------------------------ */
/** Remove session from manager
* @param session The session to remove
* @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
* {@link SessionIdManager#invalidateAll(String)} should be called.
*/
@Override
public void removeSession(AbstractSession session, boolean invalidate)
{
// Remove session from context and global maps
boolean removed = false;
synchronized (this)
{
//take this session out of the map of sessions for this context
if (getSession(session.getClusterId()) != null)
{
removed = true;
removeSession(session.getClusterId());
}
}
if (removed)
{
// Remove session from all context and global id maps
_sessionIdManager.removeSession(session);
if (invalidate)
_sessionIdManager.invalidateAll(session.getClusterId());
if (invalidate && !_sessionListeners.isEmpty())
{
HttpSessionEvent event=new HttpSessionEvent(session);
for (HttpSessionListener l : _sessionListeners)
l.sessionDestroyed(event);
}
if (!invalidate)
{
session.willPassivate();
}
}
}
/**
* Expire any Sessions we have in memory matching the list of
* expired Session ids.
*
* @param sessionIds
*/
protected void expire (List> sessionIds)
{
//don't attempt to scavenge if we are shutting down
if (isStopping() || isStopped())
return;
//Remove any sessions we already have in memory that match the ids
Thread thread=Thread.currentThread();
ClassLoader old_loader=thread.getContextClassLoader();
ListIterator> itor = sessionIds.listIterator();
try
{
while (itor.hasNext())
{
String sessionId = (String)itor.next();
if (LOG.isDebugEnabled())
LOG.debug("Expiring session id "+sessionId);
Session session = (Session)_sessions.get(sessionId);
if (session != null)
{
session.timeout();
itor.remove();
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Unrecognized session id="+sessionId);
}
}
}
catch (Throwable t)
{
LOG.warn("Problem expiring sessions", t);
}
finally
{
thread.setContextClassLoader(old_loader);
}
}
/**
* Load a session from the database
* @param id
* @return the session data that was loaded
* @throws Exception
*/
protected SessionData loadSession (final String id, final String canonicalContextPath, final String vhost)
throws Exception
{
final AtomicReference _reference = new AtomicReference();
final AtomicReference _exception = new AtomicReference();
Runnable load = new Runnable()
{
@SuppressWarnings("unchecked")
public void run()
{
SessionData data = null;
Connection connection=null;
PreparedStatement statement = null;
try
{
connection = getConnection();
statement = _jdbcSessionIdMgr._dbAdaptor.getLoadStatement(connection, id, canonicalContextPath, vhost);
ResultSet result = statement.executeQuery();
if (result.next())
{
data = new SessionData(id);
data.setRowId(result.getString(_jdbcSessionIdMgr._sessionTableRowId));
data.setCookieSet(result.getLong("cookieTime"));
data.setLastAccessed(result.getLong("lastAccessTime"));
data.setAccessed (result.getLong("accessTime"));
data.setCreated(result.getLong("createTime"));
data.setLastNode(result.getString("lastNode"));
data.setLastSaved(result.getLong("lastSavedTime"));
data.setExpiryTime(result.getLong("expiryTime"));
data.setCanonicalContext(result.getString("contextPath"));
data.setVirtualHost(result.getString("virtualHost"));
InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map");
ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is);
Object o = ois.readObject();
data.setAttributeMap((Map)o);
ois.close();
if (LOG.isDebugEnabled())
LOG.debug("LOADED session "+data);
}
_reference.set(data);
}
catch (Exception e)
{
_exception.set(e);
}
finally
{
if (connection!=null)
{
try { connection.close();}
catch(Exception e) { LOG.warn(e); }
}
}
}
};
if (_context==null)
load.run();
else
_context.getContextHandler().handle(load);
if (_exception.get()!=null)
throw _exception.get();
return _reference.get();
}
/**
* Insert a session into the database.
*
* @param data
* @throws Exception
*/
protected void storeSession (SessionData data)
throws Exception
{
if (data==null)
return;
//put into the database
Connection connection = getConnection();
PreparedStatement statement = null;
try
{
String rowId = calculateRowId(data);
long now = System.currentTimeMillis();
connection.setAutoCommit(true);
statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession);
statement.setString(1, rowId); //rowId
statement.setString(2, data.getId()); //session id
statement.setString(3, data.getCanonicalContext()); //context path
statement.setString(4, data.getVirtualHost()); //first vhost
statement.setString(5, getSessionIdManager().getWorkerName());//my node id
statement.setLong(6, data.getAccessed());//accessTime
statement.setLong(7, data.getLastAccessed()); //lastAccessTime
statement.setLong(8, data.getCreated()); //time created
statement.setLong(9, data.getCookieSet());//time cookie was set
statement.setLong(10, now); //last saved time
statement.setLong(11, data.getExpiryTime());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(data.getAttributeMap());
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob
statement.executeUpdate();
data.setRowId(rowId); //set it on the in-memory data as well as in db
data.setLastSaved(now);
if (LOG.isDebugEnabled())
LOG.debug("Stored session "+data);
}
finally
{
if (connection!=null)
connection.close();
}
}
/**
* Update data on an existing persisted session.
*
* @param data
* @throws Exception
*/
protected void updateSession (SessionData data)
throws Exception
{
if (data==null)
return;
Connection connection = getConnection();
PreparedStatement statement = null;
try
{
long now = System.currentTimeMillis();
connection.setAutoCommit(true);
statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession);
statement.setString(1, data.getId());
statement.setString(2, getSessionIdManager().getWorkerName());//my node id
statement.setLong(3, data.getAccessed());//accessTime
statement.setLong(4, data.getLastAccessed()); //lastAccessTime
statement.setLong(5, now); //last saved time
statement.setLong(6, data.getExpiryTime());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(data.getAttributeMap());
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
statement.setBinaryStream(7, bais, bytes.length);//attribute map as blob
statement.setString(8, data.getRowId()); //rowId
statement.executeUpdate();
data.setLastSaved(now);
if (LOG.isDebugEnabled())
LOG.debug("Updated session "+data);
}
finally
{
if (connection!=null)
connection.close();
}
}
/**
* Update the node on which the session was last seen to be my node.
*
* @param data
* @throws Exception
*/
protected void updateSessionNode (SessionData data)
throws Exception
{
String nodeId = getSessionIdManager().getWorkerName();
Connection connection = getConnection();
PreparedStatement statement = null;
try
{
connection.setAutoCommit(true);
statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode);
statement.setString(1, nodeId);
statement.setString(2, data.getRowId());
statement.executeUpdate();
statement.close();
if (LOG.isDebugEnabled())
LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
}
finally
{
if (connection!=null)
connection.close();
}
}
/**
* Persist the time the session was last accessed.
*
* @param data
* @throws Exception
*/
private void updateSessionAccessTime (SessionData data)
throws Exception
{
Connection connection = getConnection();
PreparedStatement statement = null;
try
{
long now = System.currentTimeMillis();
connection.setAutoCommit(true);
statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime);
statement.setString(1, getSessionIdManager().getWorkerName());
statement.setLong(2, data.getAccessed());
statement.setLong(3, data.getLastAccessed());
statement.setLong(4, now);
statement.setLong(5, data.getExpiryTime());
statement.setString(6, data.getRowId());
statement.executeUpdate();
data.setLastSaved(now);
statement.close();
if (LOG.isDebugEnabled())
LOG.debug("Updated access time session id="+data.getId());
}
finally
{
if (connection!=null)
connection.close();
}
}
/**
* Delete a session from the database. Should only be called
* when the session has been invalidated.
*
* @param data
* @throws Exception
*/
protected void deleteSession (SessionData data)
throws Exception
{
Connection connection = getConnection();
PreparedStatement statement = null;
try
{
connection.setAutoCommit(true);
statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession);
statement.setString(1, data.getRowId());
statement.executeUpdate();
if (LOG.isDebugEnabled())
LOG.debug("Deleted Session "+data);
}
finally
{
if (connection!=null)
connection.close();
}
}
/**
* Get a connection from the driver.
* @return
* @throws SQLException
*/
private Connection getConnection ()
throws SQLException
{
return ((JDBCSessionIdManager)getSessionIdManager()).getConnection();
}
/**
* Calculate a unique id for this session across the cluster.
*
* Unique id is composed of: contextpath_virtualhost0_sessionid
* @param data
* @return
*/
private String calculateRowId (SessionData data)
{
String rowId = canonicalize(_context.getContextPath());
rowId = rowId + "_" + getVirtualHost(_context);
rowId = rowId+"_"+data.getId();
return rowId;
}
/**
* Get the first virtual host for the context.
*
* Used to help identify the exact session/contextPath.
*
* @return 0.0.0.0 if no virtual host is defined
*/
private String getVirtualHost (ContextHandler.Context context)
{
String vhost = "0.0.0.0";
if (context==null)
return vhost;
String [] vhosts = context.getContextHandler().getVirtualHosts();
if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
return vhost;
return vhosts[0];
}
/**
* Make an acceptable file name from a context path.
*
* @param path
* @return
*/
private String canonicalize (String path)
{
if (path==null)
return "";
return path.replace('/', '_').replace('.','_').replace('\\','_');
}
}