org.ireland.jnetty.server.session.SessionManager.java Source code

Java tutorial

Introduction

Here is the source code for org.ireland.jnetty.server.session.SessionManager.java

Source

/*
 * 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;

    }
}