/*
* Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package org.ireland.jnetty.server.session;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import javax.servlet.SessionCookieConfig;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.ireland.jnetty.config.ConfigException;
import org.ireland.jnetty.webapp.WebApp;
import com.caucho.util.Crc64;
import com.caucho.util.LruCache;
import com.caucho.util.RandomUtil;
/**
* Manages sessions in a web-webApp.
*/
public final class SessionManager implements SessionCookieConfig
{
private static final Log log = LogFactory.getLog(SessionManager.class.getName());
private static final boolean debug = log.isDebugEnabled();
private static final int FALSE = 0;
private static final int COOKIE = 1;
private static final int TRUE = 2;
private static final int UNSET = 0;
private static final int SET_TRUE = 1;
private static final int SET_FALSE = 2;
private static final int[] DECODE;
private final WebApp _webApp;
// active sessions
private LruCache<String, HttpSessionImpl> _sessions;
// allow session rewriting
private boolean _enableSessionUrls = true;
// maximum number of sessions
private int _sessionMax = 8192;
// how long a session will be inactive before it times out
private long _sessionTimeout = 30 * 60 * 1000;
private String _cookieName = "JSESSIONID";
private String _sslCookieName;
// Rewriting strings.
private String _sessionSuffix = ";jsessionid=";
private String _sessionPrefix;
// default cookie version
private int _cookieVersion;
private String _cookieDomain;
private String _cookieDomainRegexp;
private String _cookiePath;
private long _cookieMaxAge = 24 * 60 * 60; //1 day
private int _isCookieHttpOnly;
private String _cookieComment;
private String _cookiePort;
private int _cookieLength = 21;
// Servlet 3.0 plain | ssl session tracking cookies become secure when set to true
private boolean _isSecure;
// List of the HttpSessionListeners from the configuration file
private ArrayList<HttpSessionListener> _listeners;
// List of the HttpSessionListeners from the configuration file
private ArrayList<HttpSessionActivationListener> _activationListeners;
// List of the HttpSessionAttributeListeners from the configuration file
private ArrayList<HttpSessionAttributeListener> _attributeListeners;
private boolean _isClosed;
/**
* Creates and initializes a new session manager
*
* @param webApp
* the web-webApp webApp
*/
public SessionManager(WebApp webApp)
{
_webApp = webApp;
_sessions = new LruCache<String, HttpSessionImpl>(_sessionMax);
_cookiePath = _webApp.getContextPath();
if (_cookiePath == null || "".equals(_cookiePath))
_cookiePath = "/";
}
/**
* Returns the session prefix, ie.. ";jsessionid=".
*/
public String getSessionPrefix()
{
return _sessionSuffix;
}
/**
* Returns the alternate session prefix, before the URL for wap.
*/
public String getAlternateSessionPrefix()
{
return _sessionPrefix;
}
/**
* Returns the cookie version.
*/
public int getCookieVersion()
{
return _cookieVersion;
}
/**
* Sets the cookie version.
*/
public void setCookieVersion(int cookieVersion)
{
_cookieVersion = cookieVersion;
}
/**
* Sets the cookie ports.
*/
public void setCookiePort(String port)
{
_cookiePort = port;
}
/**
* Gets the cookie ports.
*/
public String getCookiePort()
{
return _cookiePort;
}
/**
* Returns the SessionManager's webApp
*/
public WebApp getWebApp()
{
return _webApp;
}
/**
* Returns the current number of active sessions.
*/
public int getActiveSessionCount()
{
if (_sessions == null)
return -1;
else
return _sessions.size();
}
/**
* Returns the active sessions.
*/
public int getSessionActiveCount()
{
return getActiveSessionCount();
}
/**
* Adds a new HttpSessionListener.
*/
public void addListener(HttpSessionListener listener)
{
if (_listeners == null)
_listeners = new ArrayList<HttpSessionListener>();
_listeners.add(listener);
}
/**
* Adds a new HttpSessionListener.
*/
ArrayList<HttpSessionListener> getListeners()
{
return _listeners;
}
/**
* Adds a new HttpSessionActivationListener.
*/
public void addActivationListener(HttpSessionActivationListener listener)
{
if (_activationListeners == null)
_activationListeners = new ArrayList<HttpSessionActivationListener>();
_activationListeners.add(listener);
}
/**
* Returns the activation listeners.
*/
ArrayList<HttpSessionActivationListener> getActivationListeners()
{
return _activationListeners;
}
/**
* Adds a new HttpSessionAttributeListener.
*/
public void addAttributeListener(HttpSessionAttributeListener listener)
{
if (_attributeListeners == null)
_attributeListeners = new ArrayList<HttpSessionAttributeListener>();
_attributeListeners.add(listener);
}
/**
* Gets the HttpSessionAttributeListener.
*/
ArrayList<HttpSessionAttributeListener> getAttributeListeners()
{
return _attributeListeners;
}
/**
* Returns true if the sessions are closed.
*/
public boolean isClosed()
{
return _isClosed;
}
/**
* Returns the default session timeout in milliseconds.
*/
public long getSessionTimeout()
{
return _sessionTimeout;
}
/**
* Set the default session timeout in minutes
*/
public void setSessionTimeout(long timeout)
{
if (timeout <= 0 || Integer.MAX_VALUE / 2 < timeout)
_sessionTimeout = Long.MAX_VALUE / 2;
else
_sessionTimeout = 60000L * timeout;
}
/**
* Returns the idle time.
*/
public long getMaxIdleTime()
{
return _sessionTimeout;
}
/**
* Returns the maximum number of sessions.
*/
public int getSessionMax()
{
return _sessionMax;
}
/**
* Returns the maximum number of sessions.
*/
public void setSessionMax(int max)
{
if (max < 1)
throw new ConfigException("session-max '["+max+"]' is too small. session-max must be a positive number");
_sessionMax = max;
}
/**
* Returns true if sessions can use the session rewriting.
*/
public boolean enableSessionUrls()
{
return _enableSessionUrls;
}
/**
* Returns true if sessions can use the session rewriting.
*/
public void setEnableUrlRewriting(boolean enableUrls)
{
_enableSessionUrls = enableUrls;
}
// SessionCookieConfig implementation (Servlet 3.0)
@Override
public void setName(String name)
{
setCookieName(name);
}
@Override
public String getName()
{
return getCookieName();
}
@Override
public void setDomain(String domain)
{
setCookieDomain(domain);
}
@Override
public String getDomain()
{
return getCookieDomain();
}
@Override
public void setPath(String path)
{
_cookiePath = path;
}
@Override
public String getPath()
{
return _cookiePath;
}
@Override
public void setComment(String comment)
{
_cookieComment = comment;
}
@Override
public String getComment()
{
return _cookieComment;
}
@Override
public void setHttpOnly(boolean httpOnly)
{
setCookieHttpOnly(httpOnly);
}
@Override
public boolean isHttpOnly()
{
return isCookieHttpOnly();
}
@Override
public void setSecure(boolean secure)
{
_isSecure = secure;
}
@Override
public boolean isSecure()
{
return _isSecure;
}
@Override
public void setMaxAge(int maxAge)
{
_cookieMaxAge = maxAge * 1000;
}
@Override
public int getMaxAge()
{
return (int) (_cookieMaxAge / 1000);
}
public void setCookieName(String cookieName)
{
_cookieName = cookieName;
}
/**
* Returns the default cookie name.
*/
public String getCookieName()
{
return _cookieName;
}
/**
* Returns the SSL cookie name.
*/
public String getSSLCookieName()
{
if (_sslCookieName != null)
return _sslCookieName;
else
return _cookieName;
}
/**
* Returns the default session cookie domain.
*/
public String getCookieDomain()
{
return _cookieDomain;
}
/**
* Sets the default session cookie domain.
*/
public void setCookieDomain(String domain)
{
_cookieDomain = domain;
}
public String getCookieDomainRegexp()
{
return _cookieDomainRegexp;
}
public void setCookieDomainRegexp(String regexp)
{
_cookieDomainRegexp = regexp;
}
/**
* Sets the default session cookie domain.
*/
public void setCookiePath(String path)
{
_cookiePath = path;
}
/**
* Returns the max-age of the session cookie.
*/
public long getCookieMaxAge()
{
return _cookieMaxAge;
}
/**
* Sets the max-age of the session cookie.
*/
public void setCookieMaxAge(long maxAge)
{
_cookieMaxAge = maxAge;
}
/**
* Returns the secure of the session cookie.
*/
public boolean isCookieSecure()
{
if (_isSecure)
return true;
else
return !_cookieName.equals(_sslCookieName);
}
/**
* Sets the secure of the session cookie.
*/
public void setCookieSecure(boolean isSecure)
{
_isSecure = isSecure;
}
/**
* Returns the http-only of the session cookie.
*/
public boolean isCookieHttpOnly()
{
if (_isCookieHttpOnly == SET_TRUE)
return true;
else if (_isCookieHttpOnly == SET_FALSE)
return false;
else
return getWebApp().getCookieHttpOnly();
}
/**
* Sets the http-only of the session cookie.
*/
public void setCookieHttpOnly(boolean httpOnly)
{
_isCookieHttpOnly = httpOnly ? SET_TRUE : SET_FALSE;
}
/**
* Sets the cookie length
*/
public void setCookieLength(int cookieLength)
{
if (cookieLength < 7)
cookieLength = 7;
_cookieLength = cookieLength;
}
/**
* Returns the cookie length.
*/
public long getCookieLength()
{
return _cookieLength;
}
/**
* Returns true if the session exists in this manager.
*/
public boolean containsSession(String id)
{
return _sessions.get(id) != null;
}
/**
* Creates a pseudo-random session id. If there's an old id and the group matches, then use it because different
* webApps on the same matchine should use the same cookie.
*
* @param request
* current request
*/
public String createSessionId(HttpServletRequest request)
{
return createSessionId(request, false);
}
/**
* Creates a pseudo-random session id. If there's an old id and the group matches, then use it because different
* webApps on the same machine should use the same cookie.
*
* @param request
* current request
*/
public String createSessionId(HttpServletRequest request, boolean create)
{
String id;
do
{
id = createSessionIdImpl(request);
} while (create && getSession(id, 0, create, true) != null);
if (id == null || id.equals(""))
throw new RuntimeException();
return id;
}
public String createSessionIdImpl(HttpServletRequest request)
{
return createCookieValue();
}
/**
* 生成随机的SessionId(用作Cookie)
*
* @param owner
* @return
*/
protected String createCookieValue()
{
StringBuilder sb = new StringBuilder();
// this section is the host specific session index
// the most random bit is the high bit
int length = _cookieLength;
length -= sb.length();
long random = RandomUtil.getRandomLong();
for (int i = 0; i < 11 && length-- > 0; i++)
{
sb.append(convert(random));
random = random >> 6;
}
if (length > 0)
{
long time = System.currentTimeMillis();
for (int i = 0; i < 7 && length-- > 0; i++)
{
sb.append(convert(time));
time = time >> 6;
}
}
while (length > 0)
{
random = RandomUtil.getRandomLong();
for (int i = 0; i < 11 && length-- > 0; i++)
{
sb.append(convert(random));
random = random >> 6;
}
}
return sb.toString();
}
/**
* Finds a session in the session store, creating one if 'create' is true
*
* @param isCreate
* if the session doesn't exist, create it
* @param request
* current request
* @sessionId a desired sessionId or null
* @param now
* the time in milliseconds
* @param fromCookie
* true if the session id comes from a cookie
*
* @return the cached session.
*/
public HttpSessionImpl createSession(boolean isCreate, HttpServletRequest request, String sessionId, long now, boolean fromCookie)
{
if (_sessions == null)
return null;
HttpSessionImpl session = _sessions.get(sessionId);
if (session != null && !session.isValid())
{
session = null;
}
boolean isNew = false;
if (session == null && sessionId != null)
{
session = create(sessionId, now, isCreate);
isNew = true;
}
if (session != null)
{
if (session.isTimeout(now))
{
session.timeout();
session = null;
}
}
if (!isCreate)
return null;
if (sessionId == null || sessionId.length() <= 6)
{
sessionId = createSessionId(request, true);
}
session = new HttpSessionImpl(this, sessionId, now);
// If another thread has created and stored a new session,
// putIfNew will return the old session
session = _sessions.putIfNew(sessionId, session);
if (!sessionId.equals(session.getId()))
throw new IllegalStateException(sessionId + " != " + session.getId());
session.create(now, true);
handleCreateListeners(session);
return session;
}
/**
* Returns a session from the session store, returning null if there's no cached session.
*
* @param key
* the session id
* @param now
* the time in milliseconds
*
* @return the cached session.
*/
public HttpSessionImpl getSession(String key, long now, boolean create, boolean fromCookie)
{
HttpSessionImpl session;
boolean isNew = false;
boolean killSession = false;
if (_sessions == null)
return null;
session = _sessions.get(key);
if (session != null && !session.getId().equals(key))
throw new IllegalStateException(key + " != " + session.getId());
if (now <= 0) // just generating id
return session;
if (session == null)
{
session = create(key, now, create);
isNew = true;
}
if (session == null)
return null;
if (killSession && (!create))
{
_sessions.remove(key);
// XXX:
// session._isValid = false;
return null;
}
else if (isNew)
handleCreateListeners(session);
// else
// session.setAccess(now);
return session;
}
public HttpSessionImpl getSession(String key)
{
if (_sessions == null || key == null)
return null;
return _sessions.get(key);
}
/**
* Create a new session.
*
* @param oldId
* the id passed to the request. Reuse if possible.
* @param request
* - current HttpServletRequest
* @param fromCookie
*/
public HttpSessionImpl createSession(String oldId, long now, HttpServletRequest request, boolean fromCookie)
{
if (_sessions == null)
{
log.debug(this + " createSession called when sessionManager closed");
return null;
}
String id = oldId;
if (id == null || id.length() < 4)
{
id = createSessionId(request, true);
}
HttpSessionImpl session = create(id, now, true);
if (session == null)
return null;
synchronized (session)
{
session.create(now, true);
}
// after load so a reset doesn't clear any setting
handleCreateListeners(session);
return session;
}
public HttpSessionImpl createNewSession(HttpServletRequest request)
{
if (_sessions == null)
{
log.debug(this + " createSession called when sessionManager closed");
return null;
}
long creationTime = System.currentTimeMillis();
String id = createSessionId(request, true);
HttpSessionImpl session = create(id, creationTime, true);
if (session == null)
return null;
// after load so a reset doesn't clear any setting
handleCreateListeners(session);
return session;
}
/**
* Creates a session. It's already been established that the key does not currently have a session.
*/
private HttpSessionImpl create(String key, long creationTime, boolean isCreate)
{
HttpSessionImpl session = new HttpSessionImpl(this, key, creationTime);
// If another thread has created and stored a new session,
// putIfNew will return the old session
session = _sessions.putIfNew(key, session);
if (!key.equals(session.getId()))
throw new IllegalStateException(key + " != " + session.getId());
return session;
}
/**
* 发布SessionCreated事件
*
* @param session
*/
private void handleCreateListeners(HttpSessionImpl session)
{
if (_listeners != null)
{
HttpSessionEvent event = new HttpSessionEvent(session);
for (int i = 0; i < _listeners.size(); i++)
{
HttpSessionListener listener = _listeners.get(i);
listener.sessionCreated(event);
}
}
}
/**
* Adds a session from the cache.
*/
void addSession(HttpSessionImpl session)
{
_sessions.put(session.getId(), session);
}
/**
* Removes a session from the cache.
*/
void removeSession(HttpSessionImpl session)
{
_sessions.remove(session.getId());
}
public String[] sessionIdList()
{
ArrayList<String> sessionIds = new ArrayList<String>();
synchronized (_sessions)
{
Iterator<LruCache.Entry<String, HttpSessionImpl>> sessionsIterator = _sessions.iterator();
while (sessionsIterator.hasNext())
{
sessionIds.add(sessionsIterator.next().getKey());
}
}
String[] ids = new String[sessionIds.size()];
sessionIds.toArray(ids);
return ids;
}
/**
* 清理过期Session
*
* @return number of live sessions for stats
*/
public void clearInvalidSession()
{
}
/**
* Cleans up the sessions when the WebApp shuts down gracefully.
*/
public void close()
{
synchronized (this)
{
if (_isClosed)
return;
_isClosed = true;
}
if (_sessions == null)
return;
ArrayList<HttpSessionImpl> list = new ArrayList<HttpSessionImpl>();
for (int i = list.size() - 1; i >= 0; i--)
{
HttpSessionImpl session = list.get(i);
if (!session.isValid())
continue;
if (debug)
log.debug("close session " + session.getId());
try
{
if (session.isValid())
_sessions.remove(session.getId());
}
catch (Exception e)
{
log.debug( e.toString(), e);
}
}
}
/**
* Converts an integer to a printable character
*/
private static char convert(long code)
{
code = code & 0x3f;
if (code < 26)
return (char) ('a' + code);
else if (code < 52)
return (char) ('A' + code - 26);
else if (code < 62)
return (char) ('0' + code - 52);
else if (code == 62)
return '_';
else
return '-';
}
static int getServerCode(String id, int count)
{
if (id == null)
return -1;
if (count == 0)
{
return decode(id.charAt(0));
}
long hash = Crc64.generate(id);
for (int i = 0; i < count; i++)
{
hash >>= 6;
}
return (int) (hash & 0x3f);
}
private static int decode(int code)
{
return DECODE[code & 0x7f];
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _webApp.getContextPath() + "]";
}
static
{
DECODE = new int[128];
for (int i = 0; i < 64; i++)
DECODE[(int) convert(i)] = i;
}
/***
* 验证指定 session是否有效
*
* @param session
* @return
*/
public boolean isValid(HttpSessionImpl session)
{
if(session == null)
return false;
HttpSessionImpl trueSession = _sessions.get(session.getId());
if (trueSession == null)
return false;
if (trueSession == session && !trueSession.isTimeout())
return true;
return true;
}
/**
* 生成JSESSIONID 的 Cookie
* @param session
* @param contextPath
* @param secure
* @return
*/
public Cookie getSessionCookie(HttpSessionImpl session, String contextPath, boolean secure)
{
String sessionPath = contextPath;
sessionPath = (sessionPath == null || sessionPath.length() == 0) ? "/" : sessionPath;
String id = session.getId();
Cookie cookie = null;
cookie = new Cookie(_cookieName, id);
cookie.setComment(_cookieComment);
if(_cookieDomain != null)
cookie.setDomain(_cookieDomain);
cookie.setHttpOnly(isHttpOnly());
cookie.setMaxAge((int) _cookieMaxAge);
cookie.setPath(sessionPath);
cookie.setSecure(secure);
cookie.setVersion(_cookieVersion);
return cookie;
}
}