//
//  ========================================================================
//  Copyright (c) 1995-2019 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.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;

import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Locker.Lock;

AbstractSessionCache A base implementation of the SessionCache interface for managing a set of Session objects pertaining to a context in memory. This implementation ensures that multiple requests for the same session id always return the same Session object. It will delay writing out a session to the SessionDataStore until the last request exits the session. If the SessionDataStore supports passivation then the session passivation and activation listeners are called appropriately as the session is written. This implementation also supports evicting idle Session objects. An idle Session is one that is still valid, has not expired, but has not been accessed by a request for a configurable amount of time. An idle session will be first passivated before it is evicted from the cache.
/** * AbstractSessionCache * * A base implementation of the {@link SessionCache} interface for managing a set of * Session objects pertaining to a context in memory. * * This implementation ensures that multiple requests for the same session id * always return the same Session object. * * It will delay writing out a session to the SessionDataStore until the * last request exits the session. If the SessionDataStore supports passivation * then the session passivation and activation listeners are called appropriately as * the session is written. * * This implementation also supports evicting idle Session objects. An idle Session * is one that is still valid, has not expired, but has not been accessed by a * request for a configurable amount of time. An idle session will be first * passivated before it is evicted from the cache. */
@ManagedObject public abstract class AbstractSessionCache extends ContainerLifeCycle implements SessionCache { static final Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
The authoritative source of session data
/** * The authoritative source of session data */
protected SessionDataStore _sessionDataStore;
The SessionHandler related to this SessionCache
/** * The SessionHandler related to this SessionCache */
protected final SessionHandler _handler;
Information about the context to which this SessionCache pertains
/** * Information about the context to which this SessionCache pertains */
protected SessionContext _context;
When, if ever, to evict sessions: never; only when the last request for them finishes; after inactivity time (expressed as secs)
/** * When, if ever, to evict sessions: never; only when the last request for them finishes; after inactivity time (expressed as secs) */
protected int _evictionPolicy = SessionCache.NEVER_EVICT;
If true, as soon as a new session is created, it will be persisted to the SessionDataStore
/** * If true, as soon as a new session is created, it will be persisted to the SessionDataStore */
protected boolean _saveOnCreate = false;
If true, a session that will be evicted from the cache because it has been inactive too long will be saved before being evicted.
/** * If true, a session that will be evicted from the cache because it has been * inactive too long will be saved before being evicted. */
protected boolean _saveOnInactiveEviction;
If true, a Session whose data cannot be read will be deleted from the SessionDataStore.
/** * If true, a Session whose data cannot be read will be * deleted from the SessionDataStore. */
protected boolean _removeUnloadableSessions;
Create a new Session object from pre-existing session data
Params:
  • data – the session data
Returns:a new Session object
/** * Create a new Session object from pre-existing session data * * @param data the session data * @return a new Session object */
@Override public abstract Session newSession(SessionData data);
Create a new Session for a request.
Params:
  • request – the request
  • data – the session data
Returns:the new session
/** * Create a new Session for a request. * * @param request the request * @param data the session data * @return the new session */
public abstract Session newSession(HttpServletRequest request, SessionData data);
See Also:
  • newSession.newSession(HttpServletRequest, String, long, long)
/** * @see org.eclipse.jetty.server.session.SessionCache#newSession(javax.servlet.http.HttpServletRequest, java.lang.String, long, long) */
@Override public Session newSession(HttpServletRequest request, String id, long time, long maxInactiveMs) { if (LOG.isDebugEnabled()) LOG.debug("Creating new session id=" + id); Session session = newSession(request, _sessionDataStore.newSessionData(id, time, time, time, maxInactiveMs)); session.getSessionData().setLastNode(_context.getWorkerName()); try { if (isSaveOnCreate() && _sessionDataStore != null) _sessionDataStore.store(id, session.getSessionData()); } catch (Exception e) { LOG.warn("Save of new session {} failed", id, e); } return session; }
Get the session matching the key
Params:
  • id – session id
Returns:the Session object matching the id
/** * Get the session matching the key * * @param id session id * @return the Session object matching the id */
public abstract Session doGet(String id);
Put the session into the map if it wasn't already there
Params:
  • id – the identity of the session
  • session – the session object
Returns:null if the session wasn't already in the map, or the existing entry otherwise
/** * Put the session into the map if it wasn't already there * * @param id the identity of the session * @param session the session object * @return null if the session wasn't already in the map, or the existing entry otherwise */
public abstract Session doPutIfAbsent(String id, Session session);
Replace the mapping from id to oldValue with newValue
Params:
  • id – the id
  • oldValue – the old value
  • newValue – the new value
Returns:true if replacement was done
/** * Replace the mapping from id to oldValue with newValue * * @param id the id * @param oldValue the old value * @param newValue the new value * @return true if replacement was done */
public abstract boolean doReplace(String id, Session oldValue, Session newValue);
Remove the session with this identity from the store
Params:
  • id – the id
Returns:Session that was removed or null
/** * Remove the session with this identity from the store * * @param id the id * @return Session that was removed or null */
public abstract Session doDelete(String id);
PlaceHolder
/** * PlaceHolder */
protected class PlaceHolderSession extends Session {
Params:
  • handler – SessionHandler to which this session belongs
  • data – the session data
/** * @param handler SessionHandler to which this session belongs * @param data the session data */
public PlaceHolderSession(SessionHandler handler, SessionData data) { super(handler, data); } }
Params:
/** * @param handler the {@link SessionHandler} to use */
public AbstractSessionCache(SessionHandler handler) { _handler = handler; }
Returns:the SessionManger
/** * @return the SessionManger */
@Override public SessionHandler getSessionHandler() { return _handler; }
See Also:
  • initialize.initialize(SessionContext)
/** * @see org.eclipse.jetty.server.session.SessionCache#initialize(org.eclipse.jetty.server.session.SessionContext) */
@Override public void initialize(SessionContext context) { if (isStarted()) throw new IllegalStateException("Context set after session store started"); _context = context; }
See Also:
  • doStart.doStart()
/** * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() */
@Override protected void doStart() throws Exception { if (_sessionDataStore == null) throw new IllegalStateException("No session data store configured"); if (_handler == null) throw new IllegalStateException("No session manager"); if (_context == null) throw new IllegalStateException("No ContextId"); _sessionDataStore.initialize(_context); super.doStart(); }
See Also:
  • doStop.doStop()
/** * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() */
@Override protected void doStop() throws Exception { _sessionDataStore.stop(); super.doStop(); }
Returns:the SessionDataStore or null if there isn't one
/** * @return the SessionDataStore or null if there isn't one */
@Override public SessionDataStore getSessionDataStore() { return _sessionDataStore; }
See Also:
  • setSessionDataStore.setSessionDataStore(SessionDataStore)
/** * @see org.eclipse.jetty.server.session.SessionCache#setSessionDataStore(org.eclipse.jetty.server.session.SessionDataStore) */
@Override public void setSessionDataStore(SessionDataStore sessionStore) { updateBean(_sessionDataStore, sessionStore); _sessionDataStore = sessionStore; }
See Also:
  • getEvictionPolicy.getEvictionPolicy()
/** * @see org.eclipse.jetty.server.session.SessionCache#getEvictionPolicy() */
@ManagedAttribute(value = "session eviction policy", readonly = true) @Override public int getEvictionPolicy() { return _evictionPolicy; }
-1 means we never evict inactive sessions. 0 means we evict a session after the last request for it exits >0 is the number of seconds after which we evict inactive sessions from the cache
See Also:
  • setEvictionPolicy.setEvictionPolicy(int)
/** * -1 means we never evict inactive sessions. * 0 means we evict a session after the last request for it exits * >0 is the number of seconds after which we evict inactive sessions from the cache * * @see org.eclipse.jetty.server.session.SessionCache#setEvictionPolicy(int) */
@Override public void setEvictionPolicy(int evictionTimeout) { _evictionPolicy = evictionTimeout; } @ManagedAttribute(value = "immediately save new sessions", readonly = true) @Override public boolean isSaveOnCreate() { return _saveOnCreate; } @Override public void setSaveOnCreate(boolean saveOnCreate) { _saveOnCreate = saveOnCreate; }
Returns:true if sessions that can't be loaded are deleted from the store
/** * @return true if sessions that can't be loaded are deleted from the store */
@ManagedAttribute(value = "delete unreadable stored sessions", readonly = true) @Override public boolean isRemoveUnloadableSessions() { return _removeUnloadableSessions; }
If a session's data cannot be loaded from the store without error, remove it from the persistent store.
Params:
  • removeUnloadableSessions – if true unloadable sessions will be removed from session store
/** * If a session's data cannot be loaded from the store without error, remove * it from the persistent store. * * @param removeUnloadableSessions if <code>true</code> unloadable sessions will be removed from session store */
@Override public void setRemoveUnloadableSessions(boolean removeUnloadableSessions) { _removeUnloadableSessions = removeUnloadableSessions; }
Get a session object. If the session object is not in this session store, try getting the data for it from a SessionDataStore associated with the session manager.
See Also:
  • get.get(String)
/** * Get a session object. * * If the session object is not in this session store, try getting * the data for it from a SessionDataStore associated with the * session manager. * * @see org.eclipse.jetty.server.session.SessionCache#get(java.lang.String) */
@Override public Session get(String id) throws Exception { Session session = null; Exception ex = null; while (true) { session = doGet(id); if (_sessionDataStore == null) break; //can't load any session data so just return null or the session object if (session == null) { if (LOG.isDebugEnabled()) LOG.debug("Session {} not found locally, attempting to load", id); //didn't get a session, try and create one and put in a placeholder for it PlaceHolderSession phs = new PlaceHolderSession(_handler, new SessionData(id, null, null, 0, 0, 0, 0)); Lock phsLock = phs.lock(); Session s = doPutIfAbsent(id, phs); if (s == null) { //My placeholder won, go ahead and load the full session data try { session = loadSession(id); if (session == null) { //session does not exist, remove the placeholder doDelete(id); phsLock.close(); break; } try (Lock lock = session.lock()) { //swap it in instead of the placeholder boolean success = doReplace(id, phs, session); if (!success) { //something has gone wrong, it should have been our placeholder doDelete(id); session = null; LOG.warn("Replacement of placeholder for session {} failed", id); phsLock.close(); break; } else { //successfully swapped in the session session.setResident(true); phsLock.close(); break; } } } catch (Exception e) { ex = e; //remember a problem happened loading the session doDelete(id); //remove the placeholder phsLock.close(); session = null; break; } } else { //my placeholder didn't win, check the session returned phsLock.close(); try (Lock lock = s.lock()) { //is it a placeholder? or is a non-resident session? In both cases, chuck it away and start again if (!s.isResident() || s instanceof PlaceHolderSession) { session = null; continue; } session = s; break; } } } else { //check the session returned try (Lock lock = session.lock()) { //is it a placeholder? or is it passivated? In both cases, chuck it away and start again if (!session.isResident() || session instanceof PlaceHolderSession) { session = null; continue; } //got the session break; } } } if (ex != null) throw ex; return session; }
Load the info for the session from the session data store
Params:
  • id – the id
Returns:a Session object filled with data or null if the session doesn't exist
/** * Load the info for the session from the session data store * * @param id the id * @return a Session object filled with data or null if the session doesn't exist */
private Session loadSession(String id) throws Exception { SessionData data = null; Session session = null; if (_sessionDataStore == null) return null; //can't load it try { data = _sessionDataStore.load(id); if (data == null) //session doesn't exist return null; data.setLastNode(_context.getWorkerName());//we are going to manage the node session = newSession(data); return session; } catch (UnreadableSessionDataException e) { //can't load the session, delete it if (isRemoveUnloadableSessions()) _sessionDataStore.delete(id); throw e; } }
Put the Session object back into the session store. This should be called when a request exists the session. Only when the last simultaneous request exists the session will any action be taken. If there is a SessionDataStore write the session data through to it. If the SessionDataStore supports passivation, call the passivate/active listeners. If the evictionPolicy == SessionCache.EVICT_ON_SESSION_EXIT then after we have saved the session, we evict it from the cache.
See Also:
  • put.put(String, Session)
/** * Put the Session object back into the session store. * * This should be called when a request exists the session. Only when the last * simultaneous request exists the session will any action be taken. * * If there is a SessionDataStore write the session data through to it. * * If the SessionDataStore supports passivation, call the passivate/active listeners. * * If the evictionPolicy == SessionCache.EVICT_ON_SESSION_EXIT then after we have saved * the session, we evict it from the cache. * * @see org.eclipse.jetty.server.session.SessionCache#put(java.lang.String, org.eclipse.jetty.server.session.Session) */
@Override public void put(String id, Session session) throws Exception { if (id == null || session == null) throw new IllegalArgumentException("Put key=" + id + " session=" + (session == null ? "null" : session.getId())); try (Lock lock = session.lock()) { if (session.getSessionHandler() == null) throw new IllegalStateException("Session " + id + " is not managed"); if (!session.isValid()) return; //don't do anything with the session until the last request for it has finished if ((session.getRequests() <= 0)) { //save the session if (!_sessionDataStore.isPassivating()) { //if our backing datastore isn't the passivating kind, just save the session _sessionDataStore.store(id, session.getSessionData()); //if we evict on session exit, boot it from the cache if (getEvictionPolicy() == EVICT_ON_SESSION_EXIT) { if (LOG.isDebugEnabled()) LOG.debug("Eviction on request exit id={}", id); doDelete(session.getId()); session.setResident(false); } else { session.setResident(true); doPutIfAbsent(id, session); //ensure it is in our map if (LOG.isDebugEnabled()) LOG.debug("Non passivating SessionDataStore, session in SessionCache only id={}", id); } } else { //backing store supports passivation, call the listeners session.willPassivate(); if (LOG.isDebugEnabled()) LOG.debug("Session passivating id={}", id); _sessionDataStore.store(id, session.getSessionData()); if (getEvictionPolicy() == EVICT_ON_SESSION_EXIT) { //throw out the passivated session object from the map doDelete(id); session.setResident(false); if (LOG.isDebugEnabled()) LOG.debug("Evicted on request exit id={}", id); } else { //reactivate the session session.didActivate(); session.setResident(true); doPutIfAbsent(id, session);//ensure it is in our map if (LOG.isDebugEnabled()) LOG.debug("Session reactivated id={}", id); } } } else { if (LOG.isDebugEnabled()) LOG.debug("Req count={} for id={}", session.getRequests(), id); session.setResident(true); doPutIfAbsent(id, session); //ensure it is the map, but don't save it to the backing store until the last request exists } } }
Check to see if a session corresponding to the id exists. This method will first check with the object store. If it doesn't exist in the object store (might be passivated etc), it will check with the data store.
Throws:
  • Exception – the Exception
See Also:
/** * Check to see if a session corresponding to the id exists. * * This method will first check with the object store. If it * doesn't exist in the object store (might be passivated etc), * it will check with the data store. * * @throws Exception the Exception * @see org.eclipse.jetty.server.session.SessionCache#exists(java.lang.String) */
@Override public boolean exists(String id) throws Exception { //try the object store first Session s = doGet(id); if (s != null) { try (Lock lock = s.lock()) { //wait for the lock and check the validity of the session return s.isValid(); } } //not there, so find out if session data exists for it return _sessionDataStore.exists(id); }
Check to see if this cache contains an entry for the session corresponding to the session id.
See Also:
  • contains.contains(String)
/** * Check to see if this cache contains an entry for the session * corresponding to the session id. * * @see org.eclipse.jetty.server.session.SessionCache#contains(java.lang.String) */
@Override public boolean contains(String id) throws Exception { //just ask our object cache, not the store return (doGet(id) != null); }
Remove a session object from this store and from any backing store.
See Also:
  • delete.delete(String)
/** * Remove a session object from this store and from any backing store. * * @see org.eclipse.jetty.server.session.SessionCache#delete(java.lang.String) */
@Override public Session delete(String id) throws Exception { //get the session, if its not in memory, this will load it Session session = get(id); //Always delete it from the backing data store if (_sessionDataStore != null) { boolean dsdel = _sessionDataStore.delete(id); if (LOG.isDebugEnabled()) LOG.debug("Session {} deleted in session data store {}", id, dsdel); } //delete it from the session object store if (session != null) { session.setResident(false); } return doDelete(id); }
See Also:
  • checkExpiration.checkExpiration(Set)
/** * @see org.eclipse.jetty.server.session.SessionCache#checkExpiration(Set) */
@Override public Set<String> checkExpiration(Set<String> candidates) { if (!isStarted()) return Collections.emptySet(); if (LOG.isDebugEnabled()) LOG.debug("{} checking expiration on {}", this, candidates); Set<String> allCandidates = _sessionDataStore.getExpired(candidates); Set<String> sessionsInUse = new HashSet<>(); if (allCandidates != null) { for (String c : allCandidates) { Session s = doGet(c); if (s != null && s.getRequests() > 0) //if the session is in my cache, check its not in use first sessionsInUse.add(c); } try { allCandidates.removeAll(sessionsInUse); } catch (UnsupportedOperationException e) { Set<String> tmp = new HashSet<>(allCandidates); tmp.removeAll(sessionsInUse); allCandidates = tmp; } } return allCandidates; }
Check a session for being inactive and thus being able to be evicted, if eviction is enabled.
Params:
  • session – session to check
/** * Check a session for being inactive and * thus being able to be evicted, if eviction * is enabled. * * @param session session to check */
@Override public void checkInactiveSession(Session session) { if (session == null) return; if (LOG.isDebugEnabled()) LOG.debug("Checking for idle {}", session.getId()); try (Lock s = session.lock()) { if (getEvictionPolicy() > 0 && session.isIdleLongerThan(getEvictionPolicy()) && session.isValid() && session.isResident() && session.getRequests() <= 0) { //Be careful with saveOnInactiveEviction - you may be able to re-animate a session that was //being managed on another node and has expired. try { if (LOG.isDebugEnabled()) LOG.debug("Evicting idle session {}", session.getId()); //save before evicting if (isSaveOnInactiveEviction() && _sessionDataStore != null) { if (_sessionDataStore.isPassivating()) session.willPassivate(); _sessionDataStore.store(session.getId(), session.getSessionData()); } doDelete(session.getId()); //detach from this cache session.setResident(false); } catch (Exception e) { LOG.warn("Passivation of idle session {} failed", session.getId(), e); //session.updateInactivityTimer(); } } } } @Override public Session renewSessionId(String oldId, String newId, String oldExtendedId, String newExtendedId) throws Exception { if (StringUtil.isBlank(oldId)) throw new IllegalArgumentException("Old session id is null"); if (StringUtil.isBlank(newId)) throw new IllegalArgumentException("New session id is null"); Session session = get(oldId); renewSessionId(session, newId, newExtendedId); return session; }
Swap the id on a session.
Params:
  • session – the session for which to do the swap
  • newId – the new id
  • newExtendedId – the full id plus node id
Throws:
  • Exception – if there was a failure saving the change
/** * Swap the id on a session. * * @param session the session for which to do the swap * @param newId the new id * @param newExtendedId the full id plus node id * @throws Exception if there was a failure saving the change */
protected void renewSessionId(Session session, String newId, String newExtendedId) throws Exception { if (session == null) return; try (Lock lock = session.lock()) { final String oldId = session.getId(); session.checkValidForWrite(); //can't change id on invalid session session.getSessionData().setId(newId); session.getSessionData().setLastSaved(0); //pretend that the session has never been saved before to get a full save session.getSessionData().setDirty(true); //ensure we will try to write the session out session.setExtendedId(newExtendedId); //remember the new extended id session.setIdChanged(true); //session id changed doPutIfAbsent(newId, session); //put the new id into our map doDelete(oldId); //take old out of map if (_sessionDataStore != null) { _sessionDataStore.delete(oldId); //delete the session data with the old id _sessionDataStore.store(newId, session.getSessionData()); //save the session data with the new id } if (LOG.isDebugEnabled()) LOG.debug("Session id {} swapped for new id {}", oldId, newId); } }
See Also:
  • setSaveOnInactiveEviction.setSaveOnInactiveEviction(boolean)
/** * @see org.eclipse.jetty.server.session.SessionCache#setSaveOnInactiveEviction(boolean) */
@Override public void setSaveOnInactiveEviction(boolean saveOnEvict) { _saveOnInactiveEviction = saveOnEvict; }
Whether we should save a session that has been inactive before we boot it from the cache.
Returns:true if an inactive session will be saved before being evicted
/** * Whether we should save a session that has been inactive before * we boot it from the cache. * * @return true if an inactive session will be saved before being evicted */
@ManagedAttribute(value = "save sessions before evicting from cache", readonly = true) @Override public boolean isSaveOnInactiveEviction() { return _saveOnInactiveEviction; } @Override public String toString() { return String.format("%s@%x[evict=%d,removeUnloadable=%b,saveOnCreate=%b,saveOnInactiveEvict=%b]", this.getClass().getName(), this.hashCode(), _evictionPolicy, _removeUnloadableSessions, _saveOnCreate, _saveOnInactiveEviction); } }