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

org.openqa.jetty.jetty.servlet.AbstractSessionManager Maven / Gradle / Ivy

There is a newer version: 4.0.0-alpha-2
Show newest version
// ========================================================================
// $Id: AbstractSessionManager.java,v 1.53 2006/11/22 20:01:10 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package org.openqa.jetty.jetty.servlet;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.apache.commons.logging.Log;
import org.openqa.jetty.log.LogFactory;
import org.openqa.jetty.http.HttpOnlyCookie;
import org.openqa.jetty.util.LazyList;
import org.openqa.jetty.util.LogSupport;
import org.openqa.jetty.util.MultiMap;


/* ------------------------------------------------------------ */
/** An Abstract implementation of SessionManager.
 * The partial implementation of SessionManager interface provides
 * the majority of the handling required to implement a
 * SessionManager.  Concrete implementations of SessionManager based
 * on AbstractSessionManager need only implement the newSession method
 * to return a specialized version of the Session inner class that
 * provides an attribute Map.
 * 

* If the property * org.openqa.jetty.jetty.servlet.AbstractSessionManager.23Notifications is set to * true, the 2.3 servlet spec notification style will be used. *

* @version $Id: AbstractSessionManager.java,v 1.53 2006/11/22 20:01:10 gregwilkins Exp $ * @author Greg Wilkins (gregw) */ public abstract class AbstractSessionManager implements SessionManager { private static Log log = LogFactory.getLog(AbstractSessionManager.class); /* ------------------------------------------------------------ */ public final static int __distantFuture = 60*60*24*7*52*20; private final static String __NEW_SESSION_ID="org.openqa.jetty.jetty.newSessionId"; /* ------------------------------------------------------------ */ /* global Map of ID to session */ protected static MultiMap __allSessions=new MultiMap(); /* ------------------------------------------------------------ */ // Setting of max inactive interval for new sessions // -1 means no timeout private int _dftMaxIdleSecs = -1; private int _scavengePeriodMs = 30000; private String _workerName ; protected transient ArrayList _sessionListeners=new ArrayList(); protected transient ArrayList _sessionAttributeListeners=new ArrayList(); protected transient Map _sessions; protected transient Random _random; protected transient boolean _weakRandom; protected transient ServletHandler _handler; protected int _minSessions = 0; protected int _maxSessions = 0; protected boolean _crossContextSessionIDs=false; protected boolean _secureCookies=false; protected boolean _httpOnly=false; protected boolean _invalidateGlobal=true; private transient SessionScavenger _scavenger = null; /* ------------------------------------------------------------ */ public AbstractSessionManager() { this(null); } /* ------------------------------------------------------------ */ public AbstractSessionManager(Random random) { _random=random; _weakRandom=false; } /* ------------------------------------------------------------ */ /** * @return True if requested session ID are first considered for new * @deprecated use getCrossContextSessionIDs * session IDs */ public boolean getUseRequestedId() { return _crossContextSessionIDs; } /* ------------------------------------------------------------ */ /** Set Use Requested ID. * @param useRequestedId True if requested session ID are first considered for new * @deprecated use setCrossContextSessionIDs * session IDs */ public void setUseRequestedId(boolean useRequestedId) { _crossContextSessionIDs = useRequestedId; } /* ------------------------------------------------------------ */ /** * @return True if cross context session IDs are first considered for new * session IDs */ public boolean getCrossContextSessionIDs() { return _crossContextSessionIDs; } /* ------------------------------------------------------------ */ /** Set Cross Context sessions IDs * This option activates a mode where a requested session ID can be used to create a * new session. This facilitates the sharing of session cookies when cross context * dispatches use sessions. * * @param useRequestedId True if cross context session ID are first considered for new * session IDs */ public void setCrossContextSessionIDs(boolean useRequestedId) { _crossContextSessionIDs = useRequestedId; } /* ------------------------------------------------------------ */ public void initialize(ServletHandler handler) { _handler=handler; } /* ------------------------------------------------------------ */ public Map getSessionMap() { return Collections.unmodifiableMap(_sessions); } /* ------------------------------------------------------------ */ public int getSessions () { return _sessions.size (); } /* ------------------------------------------------------------ */ public int getMinSessions () { return _minSessions; } /* ------------------------------------------------------------ */ public int getMaxSessions () { return _maxSessions; } /* ------------------------------------------------------------ */ public void resetStats () { _minSessions = _sessions.size (); _maxSessions = _sessions.size (); } /* ------------------------------------------------------------ */ /* new Session ID. * If the request has a requestedSessionID which is unique, that is used. * The session ID is created as a unique random long base 36. * If the request has a jvmRoute attribute, that is appended as a * worker tag, else any worker tag set on the manager is appended. * @param request * @param created * @return Session ID. */ private String newSessionId(HttpServletRequest request,long created) { synchronized(__allSessions) { // A requested session ID can only be used if it is in the global map of // ID but not in this contexts map. Ie it is an ID in use by another context // in this server and thus we are doing a cross context dispatch. if (_crossContextSessionIDs) { String requested_id=(String)request.getAttribute(__NEW_SESSION_ID); if (requested_id==null) requested_id=request.getRequestedSessionId(); if (requested_id !=null && requested_id!=null && __allSessions.containsKey(requested_id) && !_sessions.containsKey(requested_id)) return requested_id; } // pick a new unique ID! String id=null; while (id==null || id.length()==0 || __allSessions.containsKey(id)) { long r=_weakRandom ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32)) :_random.nextLong(); r^=created; if (request!=null && request.getRemoteAddr()!=null) r^=request.getRemoteAddr().hashCode(); if (r<0) r=-r; id=Long.toString(r,36); String worker = (String)request.getAttribute("org.openqa.jetty.http.ajp.JVMRoute"); if (worker!=null) id+="."+worker; else if (_workerName!=null) id+="."+_workerName; } return id; } } /* ------------------------------------------------------------ */ public HttpSession getHttpSession(String id) { synchronized(this) { return (HttpSession)_sessions.get(id); } } /* ------------------------------------------------------------ */ public HttpSession newHttpSession(HttpServletRequest request) { Session session = newSession(request); session.setMaxInactiveInterval(_dftMaxIdleSecs); synchronized(__allSessions) { synchronized(this) { _sessions.put(session.getId(),session); __allSessions.add(session.getId(), session); if (_sessions.size() > this._maxSessions) this._maxSessions = _sessions.size (); } } HttpSessionEvent event=new HttpSessionEvent(session); for(int i=0;i<_sessionListeners.size();i++) ((HttpSessionListener)_sessionListeners.get(i)) .sessionCreated(event); if (getCrossContextSessionIDs()) request.setAttribute(__NEW_SESSION_ID, session.getId()); return session; } /* ------------------------------------------------------------ */ public Cookie getSessionCookie(HttpSession session,boolean requestIsSecure) { if (_handler.isUsingCookies()) { Cookie cookie = _handler.getSessionManager().getHttpOnly() ?new HttpOnlyCookie(SessionManager.__SessionCookie,session.getId()) :new Cookie(SessionManager.__SessionCookie,session.getId()); String domain=_handler.getServletContext().getInitParameter(SessionManager.__SessionDomain); String maxAge=_handler.getServletContext().getInitParameter(SessionManager.__MaxAge); String path=_handler.getServletContext().getInitParameter(SessionManager.__SessionPath); if (path==null) path=getCrossContextSessionIDs()?"/":_handler.getHttpContext().getContextPath(); if (path==null || path.length()==0) path="/"; if (domain!=null) cookie.setDomain(domain); if (maxAge!=null) cookie.setMaxAge(Integer.parseInt(maxAge)); else cookie.setMaxAge(-1); cookie.setSecure(requestIsSecure && getSecureCookies()); cookie.setPath(path); return cookie; } return null; } /* ------------------------------------------------------------ */ protected abstract Session newSession(HttpServletRequest request); /* ------------------------------------------------------------ */ /** Get the workname. * If set, the workername is dot appended to the session ID * and can be used to assist session affinity in a load balancer. * @return String or null */ public String getWorkerName() { return _workerName; } /* ------------------------------------------------------------ */ /** Set the workname. * If set, the workername is dot appended to the session ID * and can be used to assist session affinity in a load balancer. * @param workerName */ public void setWorkerName(String workerName) { _workerName = workerName; } /* ------------------------------------------------------------ */ /** * @return seconds */ public int getMaxInactiveInterval() { return _dftMaxIdleSecs; } /* ------------------------------------------------------------ */ /** * @param seconds */ public void setMaxInactiveInterval(int seconds) { _dftMaxIdleSecs = seconds; if (_dftMaxIdleSecs>0 && _scavengePeriodMs>_dftMaxIdleSecs*100) setScavengePeriod((_dftMaxIdleSecs+9)/10); } /* ------------------------------------------------------------ */ /** * @return seconds */ public int getScavengePeriod() { return _scavengePeriodMs/1000; } /* ------------------------------------------------------------ */ /** * @param seconds */ public void setScavengePeriod(int seconds) { if (seconds==0) seconds=60; int old_period=_scavengePeriodMs; int period = seconds*1000; if (period>60000) period=60000; if (period<1000) period=1000; if (period!=old_period) { synchronized(this) { _scavengePeriodMs=period; if (_scavenger!=null) _scavenger.interrupt(); } } } /* ------------------------------------------------------------ */ /** * @return Returns the httpOnly. */ public boolean getHttpOnly() { return _httpOnly; } /* ------------------------------------------------------------ */ /** * @param httpOnly The httpOnly to set. */ public void setHttpOnly(boolean httpOnly) { _httpOnly = httpOnly; } /* ------------------------------------------------------------ */ /** * @return Returns the secureCookies. */ public boolean getSecureCookies() { return _secureCookies; } /* ------------------------------------------------------------ */ /** * @param secureCookies The secureCookies to set. */ public void setSecureCookies(boolean secureCookies) { _secureCookies = secureCookies; } /* ------------------------------------------------------------ */ public boolean isInvalidateGlobal() { return _invalidateGlobal; } /* ------------------------------------------------------------ */ /** * @param global True if session invalidation should be global. * ie Sessions in other contexts with the same ID (linked by cross context dispatch * or shared session cookie) are invalidated as a group. */ public void setInvalidateGlobal(boolean global) { _invalidateGlobal=global; } /* ------------------------------------------------------------ */ public void addEventListener(EventListener listener) throws IllegalArgumentException { if (listener instanceof HttpSessionAttributeListener) _sessionAttributeListeners.add(listener); if (listener instanceof HttpSessionListener) _sessionListeners.add(listener); } /* ------------------------------------------------------------ */ public void removeEventListener(EventListener listener) { if (listener instanceof HttpSessionAttributeListener) _sessionAttributeListeners.remove(listener); if (listener instanceof HttpSessionListener) _sessionListeners.remove(listener); } /* ------------------------------------------------------------ */ public boolean isStarted() { return _scavenger!=null; } /* ------------------------------------------------------------ */ public void start() throws Exception { if (_random==null) { log.debug("New random session seed"); try { _random=SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { log.warn("Could not generate SecureRandom for session-id randomness",e); _random=new Random(); _weakRandom=true; } _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory()); } if (_sessions==null) _sessions=new HashMap(); // Start the session scavenger if we haven't already if (_scavenger == null) { _scavenger = new SessionScavenger(); _scavenger.start(); } } /* ------------------------------------------------------------ */ public void stop() { // Invalidate all sessions to cause unbind events ArrayList sessions = new ArrayList(_sessions.values()); for (Iterator i = sessions.iterator(); i.hasNext(); ) { Session session = (Session)i.next(); session.invalidate(); } _sessions.clear(); // stop the scavenger SessionScavenger scavenger = _scavenger; _scavenger=null; if (scavenger!=null) scavenger.interrupt(); } /* -------------------------------------------------------------- */ /** Find sessions that have timed out and invalidate them. * This runs in the SessionScavenger thread. */ private void scavenge() { Thread thread = Thread.currentThread(); ClassLoader old_loader = thread.getContextClassLoader(); try { if (_handler==null) return; ClassLoader loader = _handler.getClassLoader(); if (loader!=null) thread.setContextClassLoader(loader); long now = System.currentTimeMillis(); // Since Hashtable enumeration is not safe over deletes, // we build a list of stale sessions, then go back and invalidate them Object stale=null; synchronized(AbstractSessionManager.this) { // For each session for (Iterator i = _sessions.values().iterator(); i.hasNext(); ) { Session session = (Session)i.next(); long idleTime = session._maxIdleMs; if (idleTime > 0 && session._accessed + idleTime < now) { // Found a stale session, add it to the list stale=LazyList.add(stale,session); } } } // Remove the stale sessions for (int i = LazyList.size(stale); i-->0;) { // check it has not been accessed in the meantime Session session=(Session)LazyList.get(stale,i); long idleTime = session._maxIdleMs; if (idleTime > 0 && session._accessed + idleTime < System.currentTimeMillis()) { session.invalidate(); int nbsess = this._sessions.size(); if (nbsess < this._minSessions) this._minSessions = nbsess; } } } finally { thread.setContextClassLoader(old_loader); } } /* ------------------------------------------------------------ */ public Random getRandom() { return _random; } /* ------------------------------------------------------------ */ public void setRandom(Random random) { _random=random; } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* -------------------------------------------------------------- */ /** SessionScavenger is a background thread that kills off old sessions */ class SessionScavenger extends Thread { public void run() { int period=-1; try{ while (isStarted()) { try { if (period!=_scavengePeriodMs) { if(log.isDebugEnabled())log.debug("Session scavenger period = "+_scavengePeriodMs/1000+"s"); period=_scavengePeriodMs; } sleep(period>1000?period:1000); AbstractSessionManager.this.scavenge(); } catch (InterruptedException ex){continue;} catch (Error e) {log.warn(LogSupport.EXCEPTION,e);} catch (Exception e) {log.warn(LogSupport.EXCEPTION,e);} } } finally { AbstractSessionManager.this._scavenger=null; log.debug("Session scavenger exited"); } } SessionScavenger() { super("SessionScavenger"); setDaemon(true); } } // SessionScavenger /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ public abstract class Session implements SessionManager.Session { Map _values; boolean _invalid=false; boolean _newSession=true; long _created=System.currentTimeMillis(); long _accessed=_created; long _maxIdleMs = _dftMaxIdleSecs*1000; String _id; /* ------------------------------------------------------------- */ protected Session(HttpServletRequest request) { _id=newSessionId(request,_created); if (_dftMaxIdleSecs>=0) _maxIdleMs=_dftMaxIdleSecs*1000; } /* ------------------------------------------------------------ */ protected abstract Map newAttributeMap(); /* ------------------------------------------------------------ */ public void access() { _newSession=false; _accessed=System.currentTimeMillis(); } /* ------------------------------------------------------------ */ public boolean isValid() { return !_invalid; } /* ------------------------------------------------------------ */ public ServletContext getServletContext() { return _handler.getServletContext(); } /* ------------------------------------------------------------- */ public String getId() throws IllegalStateException { return _id; } /* ------------------------------------------------------------- */ public long getCreationTime() throws IllegalStateException { if (_invalid) throw new IllegalStateException(); return _created; } /* ------------------------------------------------------------- */ public long getLastAccessedTime() throws IllegalStateException { if (_invalid) throw new IllegalStateException(); return _accessed; } /* ------------------------------------------------------------- */ public int getMaxInactiveInterval() { if (_invalid) throw new IllegalStateException(); return (int)(_maxIdleMs / 1000); } /* ------------------------------------------------------------- */ /** * @deprecated */ public HttpSessionContext getSessionContext() throws IllegalStateException { if (_invalid) throw new IllegalStateException(); return SessionContext.NULL_IMPL; } /* ------------------------------------------------------------- */ public void setMaxInactiveInterval(int secs) { _maxIdleMs = (long)secs * 1000; if (_maxIdleMs>0 && (_maxIdleMs/10)<_scavengePeriodMs) AbstractSessionManager.this.setScavengePeriod((secs+9)/10); } /* ------------------------------------------------------------- */ public void invalidate() throws IllegalStateException { if (log.isDebugEnabled()) log.debug("Invalidate session "+getId()+" in "+_handler.getHttpContext()); try { // Notify listeners and unbind values synchronized (this) { if (_invalid) throw new IllegalStateException(); if (_sessionListeners!=null) { HttpSessionEvent event=new HttpSessionEvent(this); for (int i=_sessionListeners.size(); i-->0;) ((HttpSessionListener)_sessionListeners.get(i)).sessionDestroyed(event); } if (_values!=null) { Iterator iter=_values.keySet().iterator(); while (iter.hasNext()) { String key=(String)iter.next(); Object value=_values.get(key); iter.remove(); unbindValue(key,value); if (_sessionAttributeListeners.size()>0) { HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,key,value); for (int i=0; i<_sessionAttributeListeners.size(); i++) { ((HttpSessionAttributeListener)_sessionAttributeListeners.get(i)).attributeRemoved(event); } } } } } } finally { // Remove session from context and global maps synchronized (__allSessions) { synchronized (_sessions) { _invalid=true; _sessions.remove(getId()); __allSessions.removeValue(getId(), this); if (isInvalidateGlobal()) { // Don't iterate as other sessions may also be globally invalidating while(__allSessions.containsKey(getId())) { Session session=(Session)__allSessions.getValue(getId(),0); session.invalidate(); } } } } } } /* ------------------------------------------------------------- */ public boolean isNew() throws IllegalStateException { if (_invalid) throw new IllegalStateException(); return _newSession; } /* ------------------------------------------------------------ */ public synchronized Object getAttribute(String name) { if (_invalid) throw new IllegalStateException(); if (_values==null) return null; return _values.get(name); } /* ------------------------------------------------------------ */ public synchronized Enumeration getAttributeNames() { if (_invalid) throw new IllegalStateException(); List names = _values==null?Collections.EMPTY_LIST:new ArrayList(_values.keySet()); return Collections.enumeration(names); } /* ------------------------------------------------------------ */ public synchronized void setAttribute(String name, Object value) { if (_invalid) throw new IllegalStateException(); if (_values==null) _values=newAttributeMap(); Object oldValue = _values.put(name,value); if (value==null || !value.equals(oldValue)) { unbindValue(name, oldValue); bindValue(name, value); if (_sessionAttributeListeners.size()>0) { HttpSessionBindingEvent event = new HttpSessionBindingEvent(this,name, oldValue==null?value:oldValue); for(int i=0;i<_sessionAttributeListeners.size();i++) { HttpSessionAttributeListener l = (HttpSessionAttributeListener) _sessionAttributeListeners.get(i); if (oldValue==null) l.attributeAdded(event); else if (value==null) l.attributeRemoved(event); else l.attributeReplaced(event); } } } } /* ------------------------------------------------------------ */ public synchronized void removeAttribute(String name) { if (_invalid) throw new IllegalStateException(); if (_values==null) return; Object old=_values.remove(name); if (old!=null) { unbindValue(name, old); if (_sessionAttributeListeners.size()>0) { HttpSessionBindingEvent event = new HttpSessionBindingEvent(this,name,old); for(int i=0;i<_sessionAttributeListeners.size();i++) { HttpSessionAttributeListener l = (HttpSessionAttributeListener) _sessionAttributeListeners.get(i); l.attributeRemoved(event); } } } } /* ------------------------------------------------------------- */ /** * @deprecated As of Version 2.2, this method is * replaced by {@link #getAttribute} */ public Object getValue(String name) throws IllegalStateException { return getAttribute(name); } /* ------------------------------------------------------------- */ /** * @deprecated As of Version 2.2, this method is * replaced by {@link #getAttributeNames} */ public synchronized String[] getValueNames() throws IllegalStateException { if (_invalid) throw new IllegalStateException(); if (_values==null) return new String[0]; String[] a = new String[_values.size()]; return (String[])_values.keySet().toArray(a); } /* ------------------------------------------------------------- */ /** * @deprecated As of Version 2.2, this method is * replaced by {@link #setAttribute} */ public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException { setAttribute(name,value); } /* ------------------------------------------------------------- */ /** * @deprecated As of Version 2.2, this method is * replaced by {@link #removeAttribute} */ public void removeValue(java.lang.String name) throws IllegalStateException { removeAttribute(name); } /* ------------------------------------------------------------- */ /** If value implements HttpSessionBindingListener, call valueBound() */ private void bindValue(java.lang.String name, Object value) { if (value!=null && value instanceof HttpSessionBindingListener) ((HttpSessionBindingListener)value) .valueBound(new HttpSessionBindingEvent(this,name)); } /* ------------------------------------------------------------- */ /** If value implements HttpSessionBindingListener, call valueUnbound() */ private void unbindValue(java.lang.String name, Object value) { if (value!=null && value instanceof HttpSessionBindingListener) ((HttpSessionBindingListener)value) .valueUnbound(new HttpSessionBindingEvent(this,name)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy