/*
* Copyright (c) 1998-2008 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 Emil Ong
*/
package com.caucho.quercus.lib.session;
import com.caucho.config.ConfigException;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.SessionArrayValue;
import com.caucho.util.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Stripped down version of com.caucho.server.session.SessionManager,
* customized to PHP instead of J2EE sessions.
*/
public class QuercusSessionManager {
static protected final L10N L = new L10N(QuercusSessionManager.class);
static protected final Logger log
= Logger.getLogger(QuercusSessionManager.class.getName());
private static int FALSE = 0;
private static int COOKIE = 1;
private static int TRUE = 2;
private static int SET_TRUE = 1;
private static int SET_FALSE = 2;
// active sessions
protected LruCache<String,SessionArrayValue> _sessions;
// iterator to purge sessions (to reduce gc)
protected Iterator<SessionArrayValue> _sessionIter;
// array list for session timeout
protected ArrayList<SessionArrayValue> _sessionList
= new ArrayList<SessionArrayValue>();
// maximum number of sessions
protected int _sessionMax = 4096;
private long _sessionTimeout = 30 * 60 * 1000L;
private int _reuseSessionId = COOKIE;
@SuppressWarnings("unused")
private int _cookieLength = 18;
private int _alwaysLoadSession;
private boolean _alwaysSaveSession;
private boolean _saveOnlyOnShutdown;
@SuppressWarnings("unused")
private boolean _isAppendServerIndex = false;
protected boolean _isClosed;
// statistics
protected Object _statisticsLock = new Object();
protected long _sessionCreateCount;
protected long _sessionTimeoutCount;
/**
* Creates and initializes a new session manager.
*/
public QuercusSessionManager()
{
_sessions = new LruCache<String,SessionArrayValue>(_sessionMax);
_sessionIter = _sessions.values();
}
/**
* True if sessions should always be saved.
*/
boolean getAlwaysSaveSession()
{
return _alwaysSaveSession;
}
/**
* True if sessions should always be saved.
*/
public void setAlwaysSaveSession(boolean save)
{
_alwaysSaveSession = save;
}
/**
* True if sessions should always be loadd.
*/
boolean getAlwaysLoadSession()
{
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 only be saved on shutdown.
*/
public boolean getSaveOnlyOnShutdown()
{
return _saveOnlyOnShutdown;
}
/**
* True if sessions should only be saved on shutdown.
*/
public void setSaveOnlyOnShutdown(boolean save)
{
_saveOnlyOnShutdown = save;
}
/**
* 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 cookie length
*/
public void setCookieLength(int cookieLength)
{
if (cookieLength < 7)
cookieLength = 7;
_cookieLength = cookieLength;
}
protected void setSessionTimeout(long sessionTimeout)
{
_sessionTimeout = sessionTimeout;
}
/**
* Returns the current number of active sessions.
*/
public int getActiveSessionCount()
{
if (_sessions == null)
return -1;
else
return _sessions.size();
}
/**
* Returns the created sessions.
*/
public long getSessionCreateCount()
{
return _sessionCreateCount;
}
/**
* Returns the timeout sessions.
*/
public long getSessionTimeoutCount()
{
return _sessionTimeoutCount;
}
/**
* 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 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;
}
/**
* Returns the maximum number of sessions.
*/
public int getSessionMax()
{
return _sessionMax;
}
/**
* Returns the maximum number of sessions.
*/
public void setSessionMax(int max)
{
_sessionMax = max;
}
/**
* Removes a session from the cache and deletes it from the backing store,
* if applicable.
*/
public void removeSession(String sessionId)
{
_sessions.remove(sessionId);
remove(sessionId);
}
protected void remove(String sessionId)
{
}
/**
* Loads the session.
*
* @param in the input stream containing the serialized session
* @param obj the session object to be deserialized
*/
public void load(ObjectInputStream in, Object obj)
throws IOException
{
SessionArrayValue session = (SessionArrayValue) obj;
session.load(null, in);
}
/**
* Checks if the session is empty.
*/
public boolean isEmpty(Object obj)
{
SessionArrayValue session = (SessionArrayValue) obj;
return session.isEmpty();
}
/**
* Sets module session id generation.
*/
public void setCookieAppendServerIndex(boolean isAppend)
{
_isAppendServerIndex = isAppend;
}
/**
* Create a new session.
*
* @param oldId the id passed to the request. Reuse if possible.
* @param now the current date
*
*/
public SessionArrayValue createSession(Env env, String oldId, long now)
{
String id = oldId;
if (id == null || id.length() < 4)
id = createSessionId(env);
SessionArrayValue session = create(env, id, now);
if (session == null)
return null;
synchronized (_statisticsLock) {
_sessionCreateCount++;
}
return session;
}
/**
* Creates a pseudo-random session id. If there's an old id and the
* group matches, then use it because different applications on the
* same matchine should use the same cookie.
*/
public String createSessionId(Env env)
{
String id;
do {
CharBuffer sb = new CharBuffer();
Base64.encode(sb, RandomUtil.getRandomLong());
Base64.encode(sb, System.currentTimeMillis());
id = sb.toString();
} while (getSession(env, id, 0) != null);
if (id == null || id.equals(""))
throw new RuntimeException();
return id;
}
/**
* 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. now == 0 implies
* that we're just checking for the existence of such a session in
* the cache and do not intend actually to load it if it is not.
*
* @return the cached session.
*
*/
public SessionArrayValue getSession(Env env, String key, long now)
{
SessionArrayValue session;
boolean isNew = false;
if (_sessions == null)
return null;
// Check the cache first
session = _sessions.get(key);
if (session != null && ! session.getId().equals(key))
throw new IllegalStateException(key + " != " + session.getId());
if (session != null) {
if (session.inUse()) {
return (SessionArrayValue)session.copy(env);
}
}
if (session == null)
return null;
if (isNew) {
isNew = ! load(env, session, now);
}
else if (! getSaveOnlyOnShutdown() && ! session.load()) {
// if the load failed, then the session died out from underneath
session.reset(now);
isNew = true;
}
if (! isNew)
session.setAccess(now);
return (SessionArrayValue)session.copy(env);
}
public void saveSession(Env env, SessionArrayValue session)
{
SessionArrayValue copy = (SessionArrayValue) session.copy(env);
_sessions.put(session.getId(), copy);
session.finish();
}
/**
* Creates a session. It's already been established that the
* key does not currently have a session.
*/
protected SessionArrayValue create(Env env, String key, long now)
{
SessionArrayValue session
= createSessionValue(key, now, _sessionTimeout);
load(env, session, 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 (SessionArrayValue)session.copy(env);
}
/**
* Creates a new SessionArrayValue instance.
*/
protected SessionArrayValue createSessionValue(String key, long now,
long sessionTimeout)
{
return new SessionArrayValue(key, now, _sessionTimeout);
}
/**
* Loads the session from the backing store.
*
* @param session the session to load.
* @param now current time in milliseconds. now == 0 implies
* that we're just checking for the existence of such a session in
* the cache and do not intend actually to load it if it is not.
*
*/
protected boolean load(Env env, SessionArrayValue session, long now)
{
try {
if (session.inUse()) {
return true;
}
else if (now <= 0) {
return false;
}
else if (session.load()) {
session.setAccess(now);
return true;
}
else {
session.reset(now);
}
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
session.reset(now);
}
return false;
}
/**
* Timeout for reaping old sessions.
*/
public void handleAlarm()
{
try {
_sessionList.clear();
int liveSessions = 0;
if (_isClosed)
return;
long now = System.currentTimeMillis();
synchronized (_sessions) {
_sessionIter = _sessions.values(_sessionIter);
while (_sessionIter.hasNext()) {
SessionArrayValue session = _sessionIter.next();
long maxIdleTime = session.getMaxInactiveInterval();
if (session.inUse())
liveSessions++;
else if (session.getAccessTime() + maxIdleTime < now)
_sessionList.add(session);
else
liveSessions++;
}
}
synchronized (_statisticsLock) {
_sessionTimeoutCount += _sessionList.size();
}
for (int i = 0; i < _sessionList.size(); i++) {
SessionArrayValue session = _sessionList.get(i);
try {
session.getMaxInactiveInterval();
_sessions.remove(session.getId());
session.invalidate();
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
}
}
} finally {}
}
/**
* Cleans up the sessions when the Application shuts down gracefully.
*/
public void close()
{
synchronized (this) {
if (_isClosed)
return;
_isClosed = true;
}
if (_sessions == null)
return;
//_alarm.dequeue();
_sessionList.clear();
ArrayList<SessionArrayValue> list = new ArrayList<SessionArrayValue>();
boolean isError = false;
synchronized (_sessions) {
_sessionIter = _sessions.values(_sessionIter);
while (_sessionIter.hasNext()) {
SessionArrayValue session = _sessionIter.next();
if (session.isValid())
list.add(session);
}
}
for (int i = list.size() - 1; i >= 0; i--) {
SessionArrayValue session = list.get(i);
try {
if (session.isValid()) {
synchronized (session) {
if (! session.isEmpty())
session.storeOnShutdown();
}
}
_sessions.remove(session.getId());
} catch (Exception e) {
if (! isError)
log.log(Level.WARNING, "Can't store session: " + e, e);
isError = true;
}
}
}
/**
* Notification from the cluster.
*/
public void notifyRemove(String id)
{
SessionArrayValue session = _sessions.remove(id);
if (session != null)
session.invalidate();
}
/**
* Notification from the cluster.
*/
public void notifyUpdate(String id)
{
}
/**
* Saves the session.
*/
public void store(OutputStream out, Object obj)
throws IOException
{
SessionArrayValue session = (SessionArrayValue) obj;
session.store(Env.getInstance(), out);
}
}