| // |
| // ======================================================================== |
| // Copyright (c) 1995-2014 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.sql.Statement; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import javax.servlet.SessionTrackingMode; |
| 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<String, AbstractSession> _sessions; |
| protected JDBCSessionIdManager _jdbcSessionIdMgr = null; |
| protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs |
| |
| |
| |
| |
| /** |
| * Session |
| * |
| * Session instance. |
| */ |
| public class Session extends AbstractSession |
| { |
| private static final long serialVersionUID = 5208464051134226143L; |
| |
| /** |
| * If dirty, session needs to be (re)persisted |
| */ |
| private boolean _dirty=false; |
| |
| |
| /** |
| * Time in msec since the epoch that a session cookie was set for this session |
| */ |
| private long _cookieSet; |
| |
| |
| /** |
| * Time in msec since the epoch that the session will expire |
| */ |
| private long _expiryTime; |
| |
| |
| /** |
| * Time in msec since the epoch that the session was last persisted |
| */ |
| private long _lastSaved; |
| |
| |
| /** |
| * Unique identifier of the last node to host the session |
| */ |
| private String _lastNode; |
| |
| |
| /** |
| * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts) |
| */ |
| private String _virtualHost; |
| |
| |
| /** |
| * Unique row in db for session |
| */ |
| private String _rowId; |
| |
| |
| /** |
| * Mangled context name (used to help distinguish 2 sessions with same id on different contexts) |
| */ |
| private String _canonicalContext; |
| |
| |
| /** |
| * Session from a request. |
| * |
| * @param request |
| */ |
| protected Session (HttpServletRequest request) |
| { |
| super(JDBCSessionManager.this,request); |
| int maxInterval=getMaxInactiveInterval(); |
| _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L)); |
| _virtualHost = JDBCSessionManager.getVirtualHost(_context); |
| _canonicalContext = canonicalize(_context.getContextPath()); |
| _lastNode = getSessionIdManager().getWorkerName(); |
| } |
| |
| |
| /** |
| * Session restored from database |
| * @param sessionId |
| * @param rowId |
| * @param created |
| * @param accessed |
| */ |
| protected Session (String sessionId, String rowId, long created, long accessed) |
| { |
| super(JDBCSessionManager.this, created, accessed, sessionId); |
| _rowId = rowId; |
| } |
| |
| |
| protected synchronized String getRowId() |
| { |
| return _rowId; |
| } |
| |
| protected synchronized void setRowId(String rowId) |
| { |
| _rowId = rowId; |
| } |
| |
| public synchronized void setVirtualHost (String vhost) |
| { |
| _virtualHost=vhost; |
| } |
| |
| public synchronized String getVirtualHost () |
| { |
| return _virtualHost; |
| } |
| |
| 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 setCanonicalContext(String str) |
| { |
| _canonicalContext=str; |
| } |
| |
| public synchronized String getCanonicalContext () |
| { |
| return _canonicalContext; |
| } |
| |
| public void setCookieSet (long ms) |
| { |
| _cookieSet = ms; |
| } |
| |
| public synchronized long getCookieSet () |
| { |
| return _cookieSet; |
| } |
| |
| public synchronized void setLastNode (String node) |
| { |
| _lastNode=node; |
| } |
| |
| public synchronized String getLastNode () |
| { |
| return _lastNode; |
| } |
| |
| @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 cookieSet() |
| { |
| _cookieSet = 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) |
| { |
| synchronized (this) |
| { |
| if (super.access(time)) |
| { |
| int maxInterval=getMaxInactiveInterval(); |
| _expiryTime = (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() |
| { |
| synchronized (this) |
| { |
| super.complete(); |
| try |
| { |
| if (isValid()) |
| { |
| if (_dirty) |
| { |
| //The session attributes have changed, write to the db, ensuring |
| //http passivation/activation listeners called |
| willPassivate(); |
| updateSession(this); |
| didActivate(); |
| } |
| else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L)) |
| { |
| updateSessionAccessTime(this); |
| } |
| } |
| } |
| 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(); |
| } |
| |
| @Override |
| public String toString () |
| { |
| return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+ |
| ",created="+getCreationTime()+",accessed="+getAccessed()+ |
| ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+ |
| ",lastSaved="+_lastSaved+",expiry="+_expiryTime; |
| } |
| } |
| |
| |
| |
| |
| /** |
| * ClassLoadingObjectInputStream |
| * |
| * Used to persist the session attribute map |
| */ |
| 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 its 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 = null; |
| Session memSession = (Session)_sessions.get(idInCluster); |
| |
| synchronized (this) |
| { |
| //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. |
| //Session data = null; |
| long now = System.currentTimeMillis(); |
| if (LOG.isDebugEnabled()) |
| { |
| if (memSession==null) |
| LOG.debug("getSession("+idInCluster+"): not in session map,"+ |
| " now="+now+ |
| " lastSaved="+(memSession==null?0:memSession._lastSaved)+ |
| " interval="+(_saveIntervalSec * 1000L)); |
| else |
| LOG.debug("getSession("+idInCluster+"): in session map, "+ |
| " now="+now+ |
| " lastSaved="+(memSession==null?0:memSession._lastSaved)+ |
| " interval="+(_saveIntervalSec * 1000L)+ |
| " lastNode="+memSession._lastNode+ |
| " thisNode="+getSessionIdManager().getWorkerName()+ |
| " difference="+(now - memSession._lastSaved)); |
| } |
| |
| try |
| { |
| if (memSession==null) |
| { |
| LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db."); |
| session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); |
| } |
| else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L)) |
| { |
| LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db."); |
| session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); |
| } |
| else |
| { |
| LOG.debug("getSession("+idInCluster+"): session in session map"); |
| session = memSession; |
| } |
| } |
| catch (Exception e) |
| { |
| LOG.warn("Unable to load session "+idInCluster, e); |
| return null; |
| } |
| |
| |
| //If we have a session |
| if (session != null) |
| { |
| //If the session was last used on a different node, or session doesn't exist on this node |
| if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null) |
| { |
| //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory |
| if (session._expiryTime <= 0 || session._expiryTime > now) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName()); |
| |
| session.setLastNode(getSessionIdManager().getWorkerName()); |
| _sessions.put(idInCluster, session); |
| |
| //update in db: if unable to update, session will be scavenged later |
| try |
| { |
| updateSessionNode(session); |
| session.didActivate(); |
| } |
| catch (Exception e) |
| { |
| LOG.warn("Unable to update freshly loaded session "+idInCluster, e); |
| return null; |
| } |
| } |
| else |
| { |
| LOG.debug("getSession ({}): Session has expired", idInCluster); |
| session=null; |
| } |
| |
| } |
| else |
| { |
| //the session loaded from the db and the one in memory are the same, so keep using the one in memory |
| session = memSession; |
| LOG.debug("getSession({}): Session not stale {}", idInCluster,session); |
| } |
| } |
| else |
| { |
| //No session in db with matching id and context path. |
| LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster); |
| } |
| |
| return session; |
| } |
| } |
| |
| /** |
| * 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<String, AbstractSession>(); |
| |
| 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 |
| } |
| |
| |
| /** |
| * 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); |
| } |
| 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 |
| { |
| synchronized (session) |
| { |
| session.willPassivate(); |
| storeSession(((JDBCSessionManager.Session)session)); |
| 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 Session loadSession (final String id, final String canonicalContextPath, final String vhost) |
| throws Exception |
| { |
| final AtomicReference<Session> _reference = new AtomicReference<Session>(); |
| final AtomicReference<Exception> _exception = new AtomicReference<Exception>(); |
| Runnable load = new Runnable() |
| { |
| @SuppressWarnings("unchecked") |
| public void run() |
| { |
| Session session = 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()) |
| { |
| session = new Session(id, result.getString(_jdbcSessionIdMgr._sessionTableRowId), result.getLong("createTime"), result.getLong("accessTime")); |
| session.setCookieSet(result.getLong("cookieTime")); |
| session.setLastAccessedTime(result.getLong("lastAccessTime")); |
| session.setLastNode(result.getString("lastNode")); |
| session.setLastSaved(result.getLong("lastSavedTime")); |
| session.setExpiryTime(result.getLong("expiryTime")); |
| session.setCanonicalContext(result.getString("contextPath")); |
| session.setVirtualHost(result.getString("virtualHost")); |
| |
| InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map"); |
| ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is); |
| Object o = ois.readObject(); |
| session.addAttributes((Map<String,Object>)o); |
| ois.close(); |
| |
| if (LOG.isDebugEnabled()) |
| LOG.debug("LOADED session "+session); |
| } |
| _reference.set(session); |
| } |
| catch (Exception e) |
| { |
| _exception.set(e); |
| } |
| finally |
| { |
| if (statement!=null) |
| { |
| try { statement.close(); } |
| catch(Exception e) { LOG.warn(e); } |
| } |
| |
| 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) |
| { |
| //if the session could not be restored, take its id out of the pool of currently-in-use |
| //session ids |
| _jdbcSessionIdMgr.removeSession(id); |
| throw _exception.get(); |
| } |
| |
| return _reference.get(); |
| } |
| |
| /** |
| * Insert a session into the database. |
| * |
| * @param data |
| * @throws Exception |
| */ |
| protected void storeSession (Session session) |
| throws Exception |
| { |
| if (session==null) |
| return; |
| |
| //put into the database |
| Connection connection = getConnection(); |
| PreparedStatement statement = null; |
| try |
| { |
| String rowId = calculateRowId(session); |
| |
| long now = System.currentTimeMillis(); |
| connection.setAutoCommit(true); |
| statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession); |
| statement.setString(1, rowId); //rowId |
| statement.setString(2, session.getId()); //session id |
| statement.setString(3, session.getCanonicalContext()); //context path |
| statement.setString(4, session.getVirtualHost()); //first vhost |
| statement.setString(5, getSessionIdManager().getWorkerName());//my node id |
| statement.setLong(6, session.getAccessed());//accessTime |
| statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime |
| statement.setLong(8, session.getCreationTime()); //time created |
| statement.setLong(9, session.getCookieSet());//time cookie was set |
| statement.setLong(10, now); //last saved time |
| statement.setLong(11, session.getExpiryTime()); |
| |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| ObjectOutputStream oos = new ObjectOutputStream(baos); |
| oos.writeObject(session.getAttributeMap()); |
| byte[] bytes = baos.toByteArray(); |
| |
| ByteArrayInputStream bais = new ByteArrayInputStream(bytes); |
| statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob |
| |
| statement.executeUpdate(); |
| session.setRowId(rowId); //set it on the in-memory data as well as in db |
| session.setLastSaved(now); |
| |
| |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Stored session "+session); |
| } |
| finally |
| { |
| if (statement!=null) |
| { |
| try { statement.close(); } |
| catch(Exception e) { LOG.warn(e); } |
| } |
| |
| if (connection!=null) |
| connection.close(); |
| } |
| } |
| |
| |
| /** |
| * Update data on an existing persisted session. |
| * |
| * @param data the session |
| * @throws Exception |
| */ |
| protected void updateSession (Session 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, getSessionIdManager().getWorkerName());//my node id |
| statement.setLong(2, data.getAccessed());//accessTime |
| statement.setLong(3, data.getLastAccessedTime()); //lastAccessTime |
| statement.setLong(4, now); //last saved time |
| statement.setLong(5, 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(6, bais, bytes.length);//attribute map as blob |
| statement.setString(7, data.getRowId()); //rowId |
| statement.executeUpdate(); |
| |
| data.setLastSaved(now); |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Updated session "+data); |
| } |
| finally |
| { |
| if (statement!=null) |
| { |
| try { statement.close(); } |
| catch(Exception e) { LOG.warn(e); } |
| } |
| |
| if (connection!=null) |
| connection.close(); |
| } |
| } |
| |
| |
| /** |
| * Update the node on which the session was last seen to be my node. |
| * |
| * @param data the session |
| * @throws Exception |
| */ |
| protected void updateSessionNode (Session 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 (statement!=null) |
| { |
| try { statement.close(); } |
| catch(Exception e) { LOG.warn(e); } |
| } |
| |
| if (connection!=null) |
| connection.close(); |
| } |
| } |
| |
| /** |
| * Persist the time the session was last accessed. |
| * |
| * @param data the session |
| * @throws Exception |
| */ |
| private void updateSessionAccessTime (Session 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.getLastAccessedTime()); |
| 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 (statement!=null) |
| { |
| try { statement.close(); } |
| catch(Exception e) { LOG.warn(e); } |
| } |
| |
| 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 (Session 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 (statement!=null) |
| { |
| try { statement.close(); } |
| catch(Exception e) { LOG.warn(e); } |
| } |
| |
| 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 (Session 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 static 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 static String canonicalize (String path) |
| { |
| if (path==null) |
| return ""; |
| |
| return path.replace('/', '_').replace('.','_').replace('\\','_'); |
| } |
| } |