/* * Copyright (c) 1998-2011 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 com.caucho.server.session; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.SessionCookieConfig; 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 com.caucho.cloud.network.ClusterServer; import com.caucho.cloud.topology.CloudServer; import com.caucho.config.ConfigException; import com.caucho.config.types.Period; import com.caucho.distcache.AbstractCache; import com.caucho.distcache.ByteStreamCache; import com.caucho.distcache.ClusterCache; import com.caucho.distcache.ExtCacheEntry; import com.caucho.distcache.ResinCacheBuilder.Scope; import com.caucho.env.meter.AverageSensor; import com.caucho.env.meter.MeterService; import com.caucho.hessian.io.HessianDebugInputStream; import com.caucho.hessian.io.SerializerFactory; import com.caucho.management.server.SessionManagerMXBean; import com.caucho.security.Authenticator; import com.caucho.server.cluster.Server; import com.caucho.server.distcache.CacheImpl; import com.caucho.server.distcache.PersistentStoreConfig; import com.caucho.server.webapp.WebApp; import com.caucho.util.Alarm; import com.caucho.util.AlarmListener; import com.caucho.util.L10N; import com.caucho.util.LruCache; import com.caucho.util.RandomUtil; import com.caucho.util.WeakAlarm; import com.caucho.vfs.TempOutputStream; // import com.caucho.server.http.ServletServer; // import com.caucho.server.http.VirtualHost; /** * Manages sessions in a web-webApp. */ public final class SessionManager implements SessionCookieConfig, AlarmListener { static protected final L10N L = new L10N(SessionManager.class); static protected final Logger log = Logger.getLogger(SessionManager.class.getName()); 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 SAVE_BEFORE_HEADERS = 0x1; private static final int SAVE_BEFORE_FLUSH = 0x2; private static final int SAVE_AFTER_REQUEST = 0x4; private static final int SAVE_ON_SHUTDOWN = 0x8; private final WebApp _webApp; private final SessionManagerAdmin _admin; private final Server _servletContainer; private final ClusterServer _selfServer; private final int _selfIndex; private CacheImpl _sessionStore; // active sessions private LruCache<String,SessionImpl> _sessions; // iterator to purge sessions (to reduce gc) private Iterator<SessionImpl> _sessionIter; // array list for session timeout private ArrayList<SessionImpl> _sessionList = new ArrayList<SessionImpl>(); // generate cookies private boolean _enableSessionCookies = true; // allow session rewriting private boolean _enableSessionUrls = true; private boolean _isAppendServerIndex = false; private boolean _isTwoDigitSessionIndex = false; // invalidate the session after the listeners have been called private boolean _isInvalidateAfterListener; // 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 boolean _isCookieUseContextPath; private String _cookiePath; private long _cookieMaxAge; private int _isCookieHttpOnly; private String _cookieComment; private String _cookiePort; private int _reuseSessionId = COOKIE; private int _cookieLength = 21; //Servlet 3.0 plain | ssl session tracking cookies become secure when set to true private boolean _isSecure; // persistence configuration private int _sessionSaveMode = SAVE_AFTER_REQUEST; private boolean _isPersistenceEnabled = false; private boolean _isSaveTriplicate = true; private boolean _isSaveBackup = true; private boolean _isDestroyOnLru = true; // If true, serialization errors should not be logged // XXX: changed for JSF private boolean _ignoreSerializationErrors = true; private boolean _isHessianSerialization = false; private SerializerFactory _hessianFactory; private boolean _isSerializeCollectionType = true; // 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; // // Compatibility fields // // private Store _sessionStore; private int _alwaysLoadSession; private int _alwaysSaveSession; private boolean _isClosed; private String _distributionId; private Alarm _alarm; // statistics private volatile long _sessionCreateCount; private volatile long _sessionTimeoutCount; private volatile long _sessionInvalidateCount; private final AverageSensor _sessionSaveSample; /** * Creates and initializes a new session manager * * @param webApp the web-webApp webApp */ public SessionManager(WebApp webApp) throws Exception { _webApp = webApp; _servletContainer = webApp.getServer(); if (_servletContainer == null) { throw new IllegalStateException(L.l("Server is not active in this context {0}", Thread.currentThread().getContextClassLoader())); } _selfServer = _servletContainer.getSelfServer(); _selfIndex = _selfServer.getIndex(); // copy defaults from store for backward compat PersistentStoreConfig cfg = _servletContainer.getPersistentStore(); if (cfg != null) { setAlwaysSaveSession(cfg.isAlwaysSave()); _isSaveBackup = cfg.isSaveBackup(); _isSaveTriplicate = cfg.isSaveTriplicate(); } _sessionSuffix = _servletContainer.getSessionURLPrefix(); _sessionPrefix = _servletContainer.getAlternateSessionURLPrefix(); _cookieName = _servletContainer.getSessionCookie(); _sslCookieName = _servletContainer.getSSLSessionCookie(); /* if (_sslCookieName != null && ! _sslCookieName.equals(_cookieName)) _isSecure = true; */ String hostName = webApp.getHostName(); String contextPath = webApp.getContextPath(); if (hostName == null || hostName.equals("")) hostName = "default"; String name = hostName + contextPath; if (_distributionId == null) _distributionId = name; _alarm = new WeakAlarm(this); _sessionSaveSample = MeterService.createAverageMeter("Resin|Http|Session Save", "Size"); _admin = new SessionManagerAdmin(this); } /** * Returns the admin. */ public SessionManagerMXBean getAdmin() { return _admin; } /** * 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; } /** * Sets the cookie ports. */ public void setCookieUseContextPath(boolean isCookieUseContextPath) { _isCookieUseContextPath = isCookieUseContextPath; } /** * Gets the cookie ports. */ public String getCookiePort() { return _cookiePort; } /** * Returns the debug log */ public Logger getDebug() { return log; } /** * Returns the SessionManager's webApp */ WebApp getWebApp() { return _webApp; } ClassLoader getClassLoader() { return getWebApp().getClassLoader(); } /** * Returns the SessionManager's authenticator */ Authenticator getAuthenticator() { return _webApp.getAuthenticator(); } /** * Returns the session cache */ ByteStreamCache getCache() { if (_isPersistenceEnabled) return _sessionStore; else return null; } /** * True if sessions should always be loadd. */ boolean isAlwaysLoadSession() { return _alwaysLoadSession == SET_TRUE; } /** * True if sessions should always be loadd. */ public void setAlwaysLoadSession(boolean load) { _alwaysLoadSession = load ? SET_TRUE : SET_FALSE; } /** * True if sessions should always be saved. */ boolean getAlwaysSaveSession() { return _alwaysSaveSession == SET_TRUE; } /** * True if sessions should always be saved. */ public void setAlwaysSaveSession(boolean save) { _alwaysSaveSession = save ? SET_TRUE : SET_FALSE; } /** * True if sessions should be saved on shutdown. */ public boolean isSaveOnShutdown() { return (_sessionSaveMode & SAVE_ON_SHUTDOWN) != 0; } /** * True if sessions should only be saved on shutdown. */ public boolean isSaveOnlyOnShutdown() { return (_sessionSaveMode & SAVE_ON_SHUTDOWN) == SAVE_ON_SHUTDOWN; } /** * True if sessions should be saved before the HTTP headers. */ public boolean isSaveBeforeHeaders() { return (_sessionSaveMode & SAVE_BEFORE_HEADERS) != 0; } /** * True if sessions should be saved before each flush. */ public boolean isSaveBeforeFlush() { return (_sessionSaveMode & SAVE_BEFORE_FLUSH) != 0; } /** * True if sessions should be saved after the request. */ public boolean isSaveAfterRequest() { return (_sessionSaveMode & SAVE_AFTER_REQUEST) != 0; } /** * Determines how many digits are used to encode the server */ boolean isTwoDigitSessionIndex() { return _isTwoDigitSessionIndex; } /** * Sets the save-mode: before-flush, before-headers, after-request, * on-shutdown */ public void setSaveMode(String mode) throws ConfigException { /* XXX: probably don't want to implement this. if ("before-flush".equals(mode)) { _sessionSaveMode = (SAVE_BEFORE_FLUSH| SAVE_BEFORE_HEADERS| SAVE_AFTER_REQUEST| SAVE_ON_SHUTDOWN); } else */ if ("before-headers".equals(mode)) { _sessionSaveMode = (SAVE_BEFORE_HEADERS | SAVE_ON_SHUTDOWN); } else if ("after-request".equals(mode)) { _sessionSaveMode = (SAVE_AFTER_REQUEST | SAVE_ON_SHUTDOWN); } else if ("on-shutdown".equals(mode)) { _sessionSaveMode = (SAVE_ON_SHUTDOWN); } else throw new ConfigException(L.l("'{0}' is an unknown session save-mode. Values are: before-headers, after-request, and on-shutdown.", mode)); } /** * Returns the string value of the save-mode. */ public String getSaveMode() { if (isSaveBeforeFlush()) return "before-flush"; else if (isSaveBeforeHeaders()) return "before-headers"; else if (isSaveAfterRequest()) return "after-request"; else if (isSaveOnShutdown()) return "on-shutdown"; else return "unknown"; } /** * True if sessions should only be saved on shutdown. */ public void setSaveOnlyOnShutdown(boolean save) { log.warning("<save-only-on-shutdown> is deprecated. Use <save-mode>on-shutdown</save-mode> instead"); if (save) _sessionSaveMode = SAVE_ON_SHUTDOWN; } /** * True if sessions should only be saved on shutdown. */ public void setSaveOnShutdown(boolean save) { log.warning("<save-on-shutdown> is deprecated. Use <save-only-on-shutdown> instead"); setSaveOnlyOnShutdown(save); } /** * Sets the serialization type. */ public void setSerializationType(String type) { if ("hessian".equals(type)) _isHessianSerialization = true; else if ("java".equals(type)) _isHessianSerialization = false; else throw new ConfigException(L.l("'{0}' is an unknown valud for serialization-type. The valid types are 'hessian' and 'java'.", type)); } public void setSerializeCollectionType(boolean isEnable) { _isSerializeCollectionType = isEnable; } /** * Returns true for Hessian serialization. */ public boolean isHessianSerialization() { return _isHessianSerialization; } /** * True if the session should be invalidated after the listener. */ public void setInvalidateAfterListener(boolean inv) { _isInvalidateAfterListener = inv; } /** * True if the session should be invalidated after the listener. */ public boolean isInvalidateAfterListener() { return _isInvalidateAfterListener; } /** * 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(); } /** * Returns the created sessions. */ public long getSessionCreateCount() { return _sessionCreateCount; } /** * Returns the timeout sessions. */ public long getSessionTimeoutCount() { return _sessionTimeoutCount; } /** * Returns the invalidate sessions. */ public long getSessionInvalidateCount() { return _sessionInvalidateCount; } /** * 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; } /** * True if serialization errors should just fail silently. */ boolean getIgnoreSerializationErrors() { return _ignoreSerializationErrors; } /** * True if serialization errors should just fail silently. */ public void setIgnoreSerializationErrors(boolean ignore) { _ignoreSerializationErrors = ignore; } /** * True if the server should reuse the current session id if the * session doesn't exist. */ public int getReuseSessionId() { return _reuseSessionId; } /** * True if the server should reuse the current session id if the * session doesn't exist. */ public boolean reuseSessionId(boolean fromCookie) { int reuseSessionId = _reuseSessionId; return reuseSessionId == TRUE || fromCookie && reuseSessionId == COOKIE; } /** * True if the server should reuse the current session id if the * session doesn't exist. */ public void setReuseSessionId(String reuse) throws ConfigException { if (reuse == null) _reuseSessionId = COOKIE; else if (reuse.equalsIgnoreCase("true") || reuse.equalsIgnoreCase("yes") || reuse.equalsIgnoreCase("cookie")) _reuseSessionId = COOKIE; else if (reuse.equalsIgnoreCase("false") || reuse.equalsIgnoreCase("no")) _reuseSessionId = FALSE; else if (reuse.equalsIgnoreCase("all")) _reuseSessionId = TRUE; else throw new ConfigException(L.l("'{0}' is an invalid value for reuse-session-id. 'true' or 'false' are the allowed values.", reuse)); } /** * Returns true if the sessions are closed. */ public boolean isClosed() { return _isClosed; } /** * Sets the cluster store. */ public void setUsePersistentStore(boolean enable) throws Exception { _isPersistenceEnabled = enable; } public boolean isUsePersistentStore() { return isPersistenceEnabled(); } public boolean isPersistenceEnabled() { return _isPersistenceEnabled; } public void setDestroyOnLru(boolean isDestroy) { _isDestroyOnLru = isDestroy; } public boolean isDestroyOnLru() { return _isDestroyOnLru || ! isPersistenceEnabled(); } public String getDistributionId() { return _distributionId; } public void setDistributionId(String distributionId) { _distributionId = distributionId; } /** * 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(L.l("session-max '{0}' is too small. session-max must be a positive number", max)); _sessionMax = max; } /** * Returns true if sessions use the cookie header. */ public boolean enableSessionCookies() { return _enableSessionCookies; } /** * Returns true if sessions use the cookie header. */ public void setEnableCookies(boolean enableCookies) { _enableSessionCookies = enableCookies; } /** * 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) { if (! _webApp.isInitializing()) throw new IllegalStateException(); setCookieName(name); } @Override public String getName() { return getCookieName(); } @Override public void setDomain(String domain) { if (! _webApp.isInitializing()) throw new IllegalStateException(); setCookieDomain(domain); } @Override public String getDomain() { return getCookieDomain(); } @Override public void setPath(String path) { if (! _webApp.isInitializing()) throw new IllegalStateException(); _cookiePath = path; } @Override public String getPath() { return _cookiePath; } @Override public void setComment(String comment) { if (! _webApp.isInitializing()) throw new IllegalStateException(); _cookieComment = comment; } @Override public String getComment() { return _cookieComment; } @Override public void setHttpOnly(boolean httpOnly) { if (! _webApp.isInitializing()) throw new IllegalStateException(); setCookieHttpOnly(httpOnly); } @Override public boolean isHttpOnly() { return isCookieHttpOnly(); } @Override public void setSecure(boolean secure) { if (! _webApp.isInitializing()) throw new IllegalStateException(L.l("SessionCookieConfig must be set during initialization")); _isSecure = secure; } @Override public boolean isSecure() { return _isSecure; } @Override public void setMaxAge(int maxAge) { if (! _webApp.isInitializing()) throw new IllegalStateException(); _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(Period maxAge) { _cookieMaxAge = maxAge.getPeriod(); } /** * 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; } /** * Sets module session id generation. */ public void setCookieModuloCluster(boolean isModulo) { } /** * Sets module session id generation. */ public void setCookieAppendServerIndex(boolean isAppend) { _isAppendServerIndex = isAppend; } /** * Sets module session id generation. */ public boolean isCookieAppendServerIndex() { return _isAppendServerIndex; } public void init() { if (_sessionSaveMode == SAVE_ON_SHUTDOWN && (_alwaysSaveSession == SET_TRUE || _alwaysLoadSession == SET_TRUE)) throw new ConfigException(L.l("save-mode='on-shutdown' cannot be used with <always-save-session/> or <always-load-session/>")); _sessions = new LruCache<String,SessionImpl>(_sessionMax); _sessionIter = _sessions.values(); if (_isPersistenceEnabled) { AbstractCache cacheBuilder = new ClusterCache(); cacheBuilder.setName("resin:session"); if (_isSaveTriplicate) cacheBuilder.setScopeMode(Scope.CLUSTER); else cacheBuilder.setScopeMode(Scope.LOCAL); /* else if (_isSaveBackup) cacheBuilder.setScopeMode(Scope.CLUSTER_BACKUP); else cacheBuilder.setScopeMode(Scope.CLUSTER_SINGLE); */ if (isAlwaysLoadSession()) cacheBuilder.setLocalExpireTimeoutMillis(20); cacheBuilder.setAccessedExpireTimeoutMillis(_sessionTimeout); _sessionStore = cacheBuilder.createIfAbsent(); } if (_cookiePath != null) { } else if (_isCookieUseContextPath) _cookiePath = _webApp.getContextPath(); if (_cookiePath == null || "".equals(_cookiePath)) _cookiePath = "/"; } public void start() throws Exception { _alarm.queue(60000); } /** * Returns the session store. */ public ByteStreamCache getSessionStore() { return _sessionStore; } public SessionSerializer createSessionSerializer(OutputStream os) throws IOException { if (_isHessianSerialization) { if (_hessianFactory == null) _hessianFactory = new SerializerFactory(getClassLoader()); HessianSessionSerializer ser; ser = new HessianSessionSerializer(os, _hessianFactory); ser.setSerializeCollectionType(_isSerializeCollectionType); return ser; } else return new JavaSessionSerializer(os, getClassLoader()); } public SessionDeserializer createSessionDeserializer(InputStream is) throws IOException { if (_isHessianSerialization) return new HessianSessionDeserializer(is, getClassLoader()); else return new JavaSessionDeserializer(is, getClassLoader()); } /** * 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 createCookieValue() { return createCookieValue(null); } public String createSessionIdImpl(HttpServletRequest request) { // look at caucho.session-server-id for a hint of the owner Object owner = request.getAttribute("caucho.session-server-id"); return createCookieValue(owner); } public boolean isOwner(String id) { return id.startsWith(_selfServer.getServerClusterId()); } protected String createCookieValue(Object owner) { StringBuilder sb = new StringBuilder(); // this section is the host specific session index // the most random bit is the high bit int index = _selfIndex; CloudServer server = _selfServer.getCloudServer(); if (owner == null) { } else if (owner instanceof Number) { index = ((Number) owner).intValue(); int podIndex = _selfServer.getCloudPod().getIndex(); server = _selfServer.getCluster().findServer(podIndex, index); if (server == null) server = _selfServer.getCloudServer(); } else if (owner instanceof String) { server = _selfServer.getCluster().findServer((String) owner); if (server == null) server = _selfServer.getCloudServer(); } index = server.getIndex(); ClusterServer clusterServer = server.getData(ClusterServer.class); clusterServer.generateIdPrefix(sb); // XXX: _cluster.generateBackup(sb, index); 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 = Alarm.getCurrentTime(); // The QA needs to add a millisecond for each server start so the // clustering test will work, but all the session ids are generated // based on the timestamp. So QA sessions don't have milliseconds if (Alarm.isTest()) time -= time % 1000; 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; } } if (_isAppendServerIndex) { sb.append('.'); sb.append((index + 1)); } 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 SessionImpl createSession(boolean isCreate, HttpServletRequest request, String sessionId, long now, boolean fromCookie) { if (_sessions == null) return null; SessionImpl session = _sessions.get(sessionId); if (session != null && ! session.isValid()) session = null; boolean isNew = false; if (session == null && sessionId != null && _sessionStore != null) { ExtCacheEntry entry = _sessionStore.getExtCacheEntry(sessionId); if (entry != null && ! entry.isValueNull()) { session = create(sessionId, now, isCreate); isNew = true; } } if (session != null) { if (session.isTimeout()) { session.invalidateTimeout(); session = null; } else if (session.load(isNew)) { session.addUse(); if (isCreate) { // TCK only set access on create session.setAccess(now); } return session; } else { // if the load failed, then the session died out from underneath if (! isNew) { if (log.isLoggable(Level.FINER)) log.fine(session + " load failed for existing session"); // server/0174 session.reset(0); /* session.setModified(); // Return the existing session for timing reasons, e.g. // if a second request hits before the first has finished saving return session; */ } } } if (! isCreate) return null; if (sessionId == null || sessionId.length() <= 6 || ! reuseSessionId(fromCookie)) { sessionId = createSessionId(request, true); } session = new SessionImpl(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()); if (! session.addUse()) throw new IllegalStateException(L.l("Can't use session for unknown reason")); _sessionCreateCount++; 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 SessionImpl getSession(String key, long now, boolean create, boolean fromCookie) { SessionImpl 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) { if (! session.addUse()) { session = null; } } if (session == null && _sessionStore != null) { /* if (! _objectManager.isInSessionGroup(key)) return null; */ session = create(key, now, create); if (! session.addUse()) session = null; isNew = true; } if (session == null) return null; if (isNew) { killSession = ! load(session, now, create); isNew = killSession; } else if (! session.load(isNew)) { // if the load failed, then the session died out from underneath if (log.isLoggable(Level.FINER)) log.fine(session + " load failed for existing session"); session.setModified(); isNew = true; } if (killSession && (! create || ! reuseSessionId(fromCookie))) { // XXX: session.setClosed(); session.endUse(); _sessions.remove(key); // XXX: // session._isValid = false; return null; } else if (isNew) handleCreateListeners(session); //else //session.setAccess(now); return session; } public SessionImpl getSession(String key) { if (_sessions == 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 SessionImpl createSession(String oldId, long now, HttpServletRequest request, boolean fromCookie) { if (_sessions == null) { log.fine(this + " createSession called when sessionManager closed"); return null; } String id = oldId; if (id == null || id.length() < 4 || ! reuseSessionId(fromCookie)) { // server/0175 // || ! _objectManager.isInSessionGroup(id) id = createSessionId(request, true); } SessionImpl session = create(id, now, true); if (session == null) return null; session.addUse(); _sessionCreateCount++; synchronized (session) { if (_isPersistenceEnabled && id.equals(oldId)) load(session, now, true); else session.create(now, true); } // 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 SessionImpl create(String key, long now, boolean isCreate) { SessionImpl session = new SessionImpl(this, key, now); // 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; } /** * Notification from the cluster. */ public void notifyRemove(String id) { SessionImpl session = _sessions.get(id); if (session != null) session.invalidateRemote(); } private void handleCreateListeners(SessionImpl 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); } } } /** * Loads the session from the backing store. The caller must synchronize * the session. * * @param session the session to load. * @param now current time in milliseconds. */ private boolean load(SessionImpl session, long now, boolean isCreate) { try { // XXX: session.setNeedsLoad(false); /* if (session.getUseCount() > 1) { // if used by more than just us, return true; } else*/ if (now <= 0) { return false; } else if (session.load(true)) { // load for a newly created session session.setAccess(now); return true; } else { session.create(now, isCreate); } } catch (Exception e) { log.log(Level.FINE, e.toString(), e); session.reset(now); } return false; } /** * Adds a session from the cache. */ void addSession(SessionImpl session) { _sessions.put(session.getId(), session); } /** * Removes a session from the cache. */ void removeSession(SessionImpl session) { _sessions.remove(session.getId()); } /** * Adds a new session save event */ void addSessionSaveSample(long size) { _sessionSaveSample.add(size); } /** * Returns a debug string for the session */ public String getSessionSerializationDebug(String id) { ByteStreamCache cache = getCache(); if (cache == null) return null; try { TempOutputStream os = new TempOutputStream(); if (cache.get(id, os)) { InputStream is = os.getInputStream(); StringWriter writer = new StringWriter(); HessianDebugInputStream dis = new HessianDebugInputStream(is, new PrintWriter(writer)); while (dis.read() >= 0) { } return writer.toString(); } os.close(); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return e.toString(); } return null; } /** * Timeout for reaping old sessions * * @return number of live sessions for stats */ @Override public void handleAlarm(Alarm alarm) { try { _sessionList.clear(); int liveSessions = 0; if (_isClosed) return; long now = Alarm.getCurrentTime(); synchronized (_sessions) { _sessionIter = _sessions.values(_sessionIter); while (_sessionIter.hasNext()) { SessionImpl session = _sessionIter.next(); if (session.isTimeout(now)) _sessionList.add(session); else liveSessions++; } } _sessionTimeoutCount += _sessionList.size(); for (int i = 0; i < _sessionList.size(); i++) { SessionImpl session = _sessionList.get(i); try { _sessions.remove(session.getId()); session.timeout(); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } } } finally { if (! _isClosed) _alarm.queue(60000); } } /** * Cleans up the sessions when the WebApp shuts down gracefully. */ public void close() { synchronized (this) { if (_isClosed) return; _isClosed = true; } if (_sessions == null) return; Alarm alarm = _alarm; _alarm = null; if (alarm != null) alarm.dequeue(); ArrayList<SessionImpl> list = new ArrayList<SessionImpl>(); // XXX: messy way of dealing with saveOnlyOnShutdown synchronized (_sessions) { _sessionIter = _sessions.values(_sessionIter); while (_sessionIter.hasNext()) { SessionImpl session = _sessionIter.next(); if (session.isValid()) list.add(session); } // XXX: if cleared here, will remove the session // _sessions.clear(); } boolean isError = false; for (int i = list.size() - 1; i >= 0; i--) { SessionImpl session = list.get(i); if (! session.isValid()) continue; if (log.isLoggable(Level.FINE)) log.fine("close session " + session.getId()); try { session.saveOnShutdown(); } catch (Exception e) { if (! isError) log.log(Level.WARNING, "Can't store session: " + e, e); isError = true; } try { if (session.isValid()) _sessions.remove(session.getId()); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); } } if (_admin != null) _admin.unregister(); _sessionList = new ArrayList<SessionImpl>(); } /** * 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 '-'; } @Override public String toString() { return getClass().getSimpleName() + "[" + _webApp.getContextPath() + "]"; } }