[Bug 351516] support nosql sessions

This commit is contained in:
Jesse McConnell 2011-07-26 15:00:29 -05:00
parent 5859a85919
commit 2ead7a83fe
20 changed files with 2620 additions and 0 deletions

94
jetty-nosql/pom.xml Normal file
View File

@ -0,0 +1,94 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>7.5.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-nosql</artifactId>
<name>Jetty :: NoSQL Session Managers</name>
<properties>
<jetty-version>${project.version}</jetty-version>
<junit4-version>${junit-version}</junit4-version>
<bundle-symbolic-name>${project.groupId}.mongodb</bundle-symbolic-name> </properties>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<verbose>false</verbose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Import-Package>javax.servlet.*;version="[2.5,3.0)",org.eclipse.jetty.server.session.jmx;version="[7.5,8)";resolution:=optional,,org.eclipse.jetty.*;version="[7.5,8)",*</Import-Package>
</instructions>
</configuration>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project/version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit4-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.6.1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-sessions-common</artifactId>
<version>${jetty-version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,540 @@
package org.eclipse.jetty.mongodb;
//========================================================================
//Copyright (c) 2011 Intalio, Inc.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.session.AbstractSessionIdManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
/**
* Based partially on the jdbc session id manager...
*
* Theory is that we really only need the session id manager for the local
* instance so we have something to scavenge on, namely the list of known ids
*
* this class has a timer that runs at the scavenge delay that runs a query
* for all id's known to this node and that have and old accessed value greater
* then the scavengeDelay.
*
* these found sessions are then run through the invalidateAll(id) method that
* is a bit hinky but is supposed to notify all handlers this id is now DOA and
* ought to be cleaned up. this ought to result in a save operation on the session
* that will change the valid field to false (this conjecture is unvalidated atm)
*/
public class MongoSessionIdManager extends AbstractSessionIdManager
{
private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session");
final static DBObject __version_1 = new BasicDBObject(MongoSessionManager.__VERSION,1);
final static DBObject __valid_false = new BasicDBObject(MongoSessionManager.__VALID,false);
final DBCollection _sessions;
protected Server _server;
private Timer _scavengeTimer;
private Timer _purgeTimer;
private TimerTask _scavengerTask;
private TimerTask _purgeTask;
private long _scavengeDelay = 30 * 60 * 1000; // every 30 minutes
private long _scavengePeriod = 10 * 6 * 1000; // wait at least 10 minutes
/**
* purge process is enabled by default
*/
private boolean _purge = true;
/**
* purge process would run daily by default
*/
private long _purgeDelay = 24 * 60 * 60 * 1000; // every day
/**
* how long do you want to persist sessions that are no longer
* valid before removing them completely
*/
private long _purgeInvalidAge = 24 * 60 * 60 * 1000; // default 1 day
/**
* how long do you want to leave sessions that are still valid before
* assuming they are dead and removing them
*/
private long _purgeValidAge = 7 * 24 * 60 * 60 * 1000; // default 1 week
/**
* the collection of session ids known to this manager
*
* TODO consider if this ought to be concurrent or not
*/
protected final Set<String> _sessionsIds = new HashSet<String>();
/* ------------------------------------------------------------ */
public MongoSessionIdManager(Server server) throws UnknownHostException, MongoException
{
this(server, new Mongo().getDB("HttpSessions").getCollection("sessions"));
}
/* ------------------------------------------------------------ */
public MongoSessionIdManager(Server server, DBCollection sessions)
{
super(new Random());
_server = server;
_sessions = sessions;
_sessions.ensureIndex(
BasicDBObjectBuilder.start().add("id",1).get(),
BasicDBObjectBuilder.start().add("unique",true).add("sparse",false).get());
_sessions.ensureIndex(
BasicDBObjectBuilder.start().add("id",1).add("version",1).get(),
BasicDBObjectBuilder.start().add("unique",true).add("sparse",false).get());
}
/* ------------------------------------------------------------ */
/**
* Scavenge is a process that periodically checks the tracked session
* ids of this given instance of the session id manager to see if they
* are past the point of expiration.
*/
protected void scavenge()
{
__log.debug("SessionIdManager:scavenge:called with delay" + _scavengeDelay);
synchronized (_sessionsIds)
{
/*
* run a query returning results that:
* - are in the known list of sessionIds
* - have an accessed time less then current time - the scavenger period
*
* we limit the query to return just the __ID so we are not sucking back full sessions
*/
BasicDBObject query = new BasicDBObject();
query.put(MongoSessionManager.__ID,new BasicDBObject("$in", _sessionsIds ));
query.put(MongoSessionManager.__ACCESSED, new BasicDBObject("$lt",System.currentTimeMillis() - _scavengeDelay));
DBCursor checkSessions = _sessions.find(query, new BasicDBObject(MongoSessionManager.__ID, 1));
for ( DBObject session : checkSessions )
{
invalidateAll((String)session.get(MongoSessionManager.__ID));
}
}
}
/* ------------------------------------------------------------ */
/**
* ScavengeFully is a process that periodically checks the tracked session
* ids of this given instance of the session id manager to see if they
* are past the point of expiration.
*
* NOTE: this is potentially devastating and may lead to serious session
* coherence issues, not to be used in a running cluster
*/
protected void scavengeFully()
{
__log.debug("SessionIdManager:scavengeFully");
DBCursor checkSessions = _sessions.find();
for (DBObject session : checkSessions)
{
invalidateAll((String)session.get(MongoSessionManager.__ID));
}
}
/* ------------------------------------------------------------ */
/**
* Purge is a process that cleans the mongodb cluster of old sessions that are no
* longer valid.
*
* There are two checks being done here:
*
* - if the accessed time is older then the current time minus the purge invalid age
* and it is no longer valid then remove that session
* - if the accessed time is older then the current time minus the purge valid age
* then we consider this a lost record and remove it
*
* NOTE: if your system supports long lived sessions then the purge valid age should be
* set to zero so the check is skipped.
*
* The second check was added to catch sessions that were being managed on machines
* that might have crashed without marking their sessions as 'valid=false'
*/
protected void purge()
{
BasicDBObject invalidQuery = new BasicDBObject();
invalidQuery.put(MongoSessionManager.__ACCESSED, new BasicDBObject("$lt",System.currentTimeMillis() - _purgeInvalidAge));
invalidQuery.put(MongoSessionManager.__VALID, __valid_false);
DBCursor oldSessions = _sessions.find(invalidQuery, new BasicDBObject(MongoSessionManager.__ID, 1));
for (DBObject session : oldSessions)
{
String id = (String)session.get("id");
__log.debug("MongoSessionIdManager:purging invalid " + id);
_sessions.remove(session);
}
if (_purgeValidAge != 0)
{
BasicDBObject validQuery = new BasicDBObject();
validQuery.put(MongoSessionManager.__ACCESSED,new BasicDBObject("$lt",System.currentTimeMillis() - _purgeValidAge));
validQuery.put(MongoSessionManager.__VALID, __valid_false);
oldSessions = _sessions.find(invalidQuery,new BasicDBObject(MongoSessionManager.__ID,1));
for (DBObject session : oldSessions)
{
String id = (String)session.get(MongoSessionManager.__ID);
__log.debug("MongoSessionIdManager:purging valid " + id);
_sessions.remove(session);
}
}
}
/* ------------------------------------------------------------ */
/**
* Purge is a process that cleans the mongodb cluster of old sessions that are no
* longer valid.
*
*/
protected void purgeFully()
{
BasicDBObject invalidQuery = new BasicDBObject();
invalidQuery.put(MongoSessionManager.__VALID, false);
DBCursor oldSessions = _sessions.find(invalidQuery, new BasicDBObject(MongoSessionManager.__ID, 1));
for (DBObject session : oldSessions)
{
String id = (String)session.get(MongoSessionManager.__ID);
__log.debug("MongoSessionIdManager:purging invalid " + id);
_sessions.remove(session);
}
}
/* ------------------------------------------------------------ */
public DBCollection getSessions()
{
return _sessions;
}
/* ------------------------------------------------------------ */
public boolean isPurgeEnabled()
{
return _purge;
}
/* ------------------------------------------------------------ */
public void setPurge(boolean purge)
{
this._purge = purge;
}
/* ------------------------------------------------------------ */
/**
* sets the scavengeDelay
*/
public void setScavengeDelay(long scavengeDelay)
{
this._scavengeDelay = scavengeDelay;
}
/* ------------------------------------------------------------ */
public void setScavengePeriod(long scavengePeriod)
{
this._scavengePeriod = scavengePeriod;
}
/* ------------------------------------------------------------ */
public void setPurgeDelay(long purgeDelay)
{
if ( isRunning() )
{
throw new IllegalStateException();
}
this._purgeDelay = purgeDelay;
}
/* ------------------------------------------------------------ */
public long getPurgeInvalidAge()
{
return _purgeInvalidAge;
}
/* ------------------------------------------------------------ */
/**
* sets how old a session is to be persisted past the point it is
* no longer valid
*/
public void setPurgeInvalidAge(long purgeValidAge)
{
this._purgeInvalidAge = purgeValidAge;
}
/* ------------------------------------------------------------ */
public long getPurgeValidAge()
{
return _purgeValidAge;
}
/* ------------------------------------------------------------ */
/**
* sets how old a session is to be persist past the point it is
* considered no longer viable and should be removed
*
* NOTE: set this value to 0 to disable purging of valid sessions
*/
public void setPurgeValidAge(long purgeValidAge)
{
this._purgeValidAge = purgeValidAge;
}
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
__log.debug("MongoSessionIdManager:starting");
/*
* setup the scavenger thread
*/
if (_scavengeDelay > 0)
{
_scavengeTimer = new Timer("MongoSessionIdScavenger",true);
synchronized (this)
{
if (_scavengerTask != null)
{
_scavengerTask.cancel();
}
_scavengerTask = new TimerTask()
{
@Override
public void run()
{
scavenge();
}
};
_scavengeTimer.schedule(_scavengerTask,_scavengeDelay,_scavengePeriod);
}
}
/*
* if purging is enabled, setup the purge thread
*/
if ( _purge )
{
_purgeTimer = new Timer("MongoSessionPurger", true);
synchronized (this)
{
if (_purgeTask != null)
{
_purgeTask.cancel();
}
_purgeTask = new TimerTask()
{
@Override
public void run()
{
purge();
}
};
_purgeTimer.schedule(_purgeTask,_purgeDelay);
}
}
}
/* ------------------------------------------------------------ */
@Override
protected void doStop() throws Exception
{
if (_scavengeTimer != null)
{
_scavengeTimer.cancel();
_scavengeTimer = null;
}
if (_purgeTimer != null)
{
_purgeTimer.cancel();
_purgeTimer = null;
}
super.doStop();
}
/* ------------------------------------------------------------ */
/**
* is the session id known to mongo, and is it valid
*/
@Override
public boolean idInUse(String sessionId)
{
/*
* optimize this query to only return the valid variable
*/
DBObject o = _sessions.findOne(new BasicDBObject("id",sessionId), __valid_false);
if ( o != null )
{
Boolean valid = (Boolean)o.get(MongoSessionManager.__VALID);
if ( valid == null )
{
return false;
}
return valid;
}
return false;
}
/* ------------------------------------------------------------ */
@Override
public void addSession(HttpSession session)
{
if (session == null)
{
return;
}
/*
* already a part of the index in mongo...
*/
__log.debug("MongoSessionIdManager:addSession:" + session.getId());
synchronized (_sessionsIds)
{
_sessionsIds.add(session.getId());
}
}
/* ------------------------------------------------------------ */
@Override
public void removeSession(HttpSession session)
{
if (session == null)
{
return;
}
synchronized (_sessionsIds)
{
_sessionsIds.remove(session.getId());
}
}
/* ------------------------------------------------------------ */
@Override
public void invalidateAll(String sessionId)
{
synchronized (_sessionsIds)
{
_sessionsIds.remove(sessionId);
//tell all contexts that may have a session object with this id to
//get rid of them
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
if (manager != null && manager instanceof MongoSessionManager)
{
((MongoSessionManager)manager).invalidateSession(sessionId);
}
}
}
}
}
/* ------------------------------------------------------------ */
// TODO not sure if this is correct
@Override
public String getClusterId(String nodeId)
{
int dot=nodeId.lastIndexOf('.');
return (dot>0)?nodeId.substring(0,dot):nodeId;
}
/* ------------------------------------------------------------ */
// TODO not sure if this is correct
@Override
public String getNodeId(String clusterId, HttpServletRequest request)
{
if (_workerName!=null)
return clusterId+'.'+_workerName;
return clusterId;
}
}

View File

@ -0,0 +1,547 @@
package org.eclipse.jetty.mongodb;
//========================================================================
//Copyright (c) 2011 Intalio, Inc.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.omg.CORBA._IDLTypeStub;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
public class MongoSessionManager extends NoSqlSessionManager
{
private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session");
/*
* strings used as keys or parts of keys in mongo
*/
private final static String __METADATA = "__metadata__";
public final static String __ID = "id";
private final static String __CREATED = "created";
public final static String __VALID = "valid";
public final static String __INVALIDATED = "invalidated";
public final static String __ACCESSED = "accessed";
private final static String __CONTEXT = "context";
public final static String __VERSION = __METADATA + ".version";
/**
* the context id is only set when this class has been started
*/
private String _contextId = null;
private DBCollection _sessions;
private DBObject __version_1;
/* ------------------------------------------------------------ */
public MongoSessionManager() throws UnknownHostException, MongoException
{
}
/*------------------------------------------------------------ */
@Override
public void doStart() throws Exception
{
super.doStart();
String[] hosts = getContextHandler().getVirtualHosts();
if (hosts == null || hosts.length == 0)
hosts = getContextHandler().getConnectorNames();
if (hosts == null || hosts.length == 0)
hosts = new String[]
{ "::" }; // IPv6 equiv of 0.0.0.0
String contextPath = getContext().getContextPath();
if (contextPath == null || "".equals(contextPath))
{
contextPath = "*";
}
_contextId = createContextId(hosts,contextPath);
__version_1 = new BasicDBObject(getContextKey(__VERSION),1);
}
/* ------------------------------------------------------------ */
/* (non-Javadoc)
* @see org.eclipse.jetty.server.session.AbstractSessionManager#setSessionIdManager(org.eclipse.jetty.server.SessionIdManager)
*/
@Override
public void setSessionIdManager(SessionIdManager metaManager)
{
MongoSessionIdManager msim = (MongoSessionIdManager)metaManager;
_sessions=msim.getSessions();
super.setSessionIdManager(metaManager);
}
/* ------------------------------------------------------------ */
@Override
protected synchronized Object save(NoSqlSession session, Object version, boolean activateAfterSave)
{
try
{
__log.debug("MongoSessionManager:save:" + session);
session.willPassivate();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout);
// Form query for upsert
BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
key.put(__VALID,true);
// Form updates
BasicDBObject update = new BasicDBObject();
boolean upsert = false;
BasicDBObject sets = new BasicDBObject();
BasicDBObject unsets = new BasicDBObject();
// handle new or existing
if (version == null)
{
// New session
upsert = true;
version = new Long(1);
sets.put(__CREATED,session.getCreationTime());
sets.put(getContextKey(__VERSION),version);
}
else
{
version = new Long(((Long)version).intValue() + 1);
update.put("$inc",__version_1);
}
// handle valid or invalid
if (session.isValid())
{
sets.put(__ACCESSED,session.getAccessed());
Set<String> names = session.takeDirty();
if (isSaveAllAttributes() || upsert)
{
names.addAll(session.getNames()); // note dirty may include removed names
}
for (String name : names)
{
Object value = session.getAttribute(name);
if (value == null)
unsets.put(getContextKey() + "." + encodeName(name),1);
else
sets.put(getContextKey() + "." + encodeName(name),encodeName(out,bout,value));
}
}
else
{
sets.put(__VALID,false);
sets.put(__INVALIDATED, System.currentTimeMillis());
unsets.put(getContextKey(),1);
}
// Do the upsert
if (!sets.isEmpty())
update.put("$set",sets);
if (!unsets.isEmpty())
update.put("$unset",unsets);
_sessions.update(key,update,upsert,false);
__log.debug("MongoSessionManager:save:db.sessions.update(" + key + "," + update + ",true)");
if (activateAfterSave)
session.didActivate();
return version;
}
catch (Exception e)
{
Log.warn(e);
}
return null;
}
/*------------------------------------------------------------ */
@Override
protected Object refresh(NoSqlSession session, Object version)
{
__log.debug("MongoSessionManager:refresh " + session);
// check if our in memory version is the same as what is on the disk
if (version != null)
{
DBObject o = _sessions.findOne(new BasicDBObject(__ID,session.getClusterId()),__version_1);
if (o != null)
{
Object saved = getNestedValue(o, getContextKey(__VERSION));
if (saved != null && saved.equals(version))
{
__log.debug("MongoSessionManager:refresh not needed");
return version;
}
version = saved;
}
}
// If we are here, we have to load the object
DBObject o = _sessions.findOne(new BasicDBObject(__ID,session.getClusterId()));
// If it doesn't exist, invalidate
if (o == null)
{
__log.debug("MongoSessionManager:refresh:marking invalid, no object");
session.invalidate();
return null;
}
// If it has been flagged invalid, invalidate
Boolean valid = (Boolean)o.get(__VALID);
if (valid == null || !valid)
{
__log.debug("MongoSessionManager:refresh:marking invalid, valid flag " + valid);
session.invalidate();
return null;
}
// We need to update the attributes. We will model this as a passivate,
// followed by bindings and then activation.
session.willPassivate();
try
{
session.clearAttributes();
DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
if (attrs != null)
{
for (String name : attrs.keySet())
{
if ( __METADATA.equals(name) )
{
continue;
}
String attr = decodeName(name);
Object value = decodeValue(attrs.get(name));
session.doPutOrRemove(attr,value);
session.bindValue(attr,value);
}
}
session.didActivate();
return version;
}
catch (Exception e)
{
Log.warn(e);
}
return null;
}
/*------------------------------------------------------------ */
@Override
protected synchronized NoSqlSession loadSession(String clusterId)
{
DBObject o = _sessions.findOne(new BasicDBObject(__ID,clusterId));
__log.debug("MongoSessionManager:loaded " + o);
if (o == null)
{
return null;
}
Boolean valid = (Boolean)o.get(__VALID);
if (valid == null || !valid)
{
return null;
}
try
{
Object version = o.get(getContextKey(__VERSION));
Long created = (Long)o.get(__CREATED);
Long accessed = (Long)o.get(__ACCESSED);
NoSqlSession session = new NoSqlSession(this,created,accessed,clusterId,version);
// get the attributes for the context
DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
__log.debug("MongoSessionManager:attrs: " + attrs);
if (attrs != null)
{
for (String name : attrs.keySet())
{
if ( __METADATA.equals(name) )
{
continue;
}
String attr = decodeName(name);
Object value = decodeValue(attrs.get(name));
session.doPutOrRemove(attr,value);
session.bindValue(attr,value);
}
}
session.didActivate();
return session;
}
catch (Exception e)
{
Log.warn(e);
}
return null;
}
/*------------------------------------------------------------ */
@Override
protected boolean remove(NoSqlSession session)
{
__log.debug("MongoSessionManager:remove:session " + session.getClusterId());
/*
* Check if the session exists and if it does remove the context
* associated with this session
*/
BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
DBObject o = _sessions.findOne(key,__version_1);
if (o != null)
{
BasicDBObject remove = new BasicDBObject();
BasicDBObject unsets = new BasicDBObject();
unsets.put(getContextKey(),1);
remove.put("$unsets",unsets);
_sessions.update(key,remove);
return true;
}
else
{
return false;
}
}
/*------------------------------------------------------------ */
@Override
protected void invalidateSession(String idInCluster)
{
__log.debug("MongoSessionManager:invalidateSession:invalidating " + idInCluster);
super.invalidateSession(idInCluster);
/*
* pull back the 'valid' value, we can check if its false, if is we don't need to
* reset it to false
*/
DBObject validKey = new BasicDBObject(__VALID, true);
DBObject o = _sessions.findOne(new BasicDBObject(__ID,idInCluster), validKey);
if (o != null && (Boolean)o.get(__VALID))
{
BasicDBObject update = new BasicDBObject();
BasicDBObject sets = new BasicDBObject();
sets.put(__VALID,false);
sets.put(__INVALIDATED, System.currentTimeMillis());
update.put("$set",sets);
BasicDBObject key = new BasicDBObject(__ID,idInCluster);
_sessions.update(key,update);
}
}
/*------------------------------------------------------------ */
protected String encodeName(String name)
{
return name.replace("%","%25").replace(".","%2E");
}
/*------------------------------------------------------------ */
protected String decodeName(String name)
{
return name.replace("%2E",".").replace("%25","%");
}
/*------------------------------------------------------------ */
protected Object encodeName(ObjectOutputStream out, ByteArrayOutputStream bout, Object value) throws IOException
{
if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Date)
{
return value;
}
else if (value.getClass().equals(HashMap.class))
{
BasicDBObject o = new BasicDBObject();
for (Map.Entry<?, ?> entry : ((Map<?, ?>)value).entrySet())
{
if (!(entry.getKey() instanceof String))
{
o = null;
break;
}
o.append(encodeName(entry.getKey().toString()),encodeName(out,bout,value));
}
if (o != null)
return o;
}
bout.reset();
out.reset();
out.writeUnshared(value);
out.flush();
return bout.toByteArray();
}
/*------------------------------------------------------------ */
protected Object decodeValue(Object value) throws IOException, ClassNotFoundException
{
if (value == null || value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Date)
{
return value;
}
else if (value instanceof byte[])
{
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream((byte[])value));
return in.readObject();
}
else if (value instanceof DBObject)
{
Map<String, Object> map = new HashMap<String, Object>();
for (String name : ((DBObject)value).keySet())
{
String attr = decodeName(name);
map.put(attr,decodeValue(((DBObject)value).get(name)));
}
return map;
}
else
{
throw new IllegalStateException(value.getClass().toString());
}
}
/*------------------------------------------------------------ */
private String getContextKey()
{
return __CONTEXT + "." + _contextId;
}
/*------------------------------------------------------------ */
private String getContextKey(String keybit)
{
return __CONTEXT + "." + _contextId + "." + keybit;
}
public void purge()
{
((MongoSessionIdManager)_sessionIdManager).purge();
}
public void purgeFully()
{
((MongoSessionIdManager)_sessionIdManager).purgeFully();
}
public void scavenge()
{
((MongoSessionIdManager)_sessionIdManager).scavenge();
}
public void scavengeFully()
{
((MongoSessionIdManager)_sessionIdManager).scavengeFully();
}
/*------------------------------------------------------------ */
/**
* returns the total number of session objects in the session store
*
* the count() operation itself is optimized to perform on the server side
* and avoid loading to client side.
*/
public long getSessionStoreCount()
{
return _sessions.find().count();
}
/*------------------------------------------------------------ */
/**
* MongoDB keys are . delimited for nesting so .'s are protected characters
*
* @param virtualHosts
* @param contextPath
* @return
*/
private String createContextId(String[] virtualHosts, String contextPath)
{
String contextId = virtualHosts[0] + contextPath;
contextId.replace('/', '_');
contextId.replace('.','_');
contextId.replace('\\','_');
return contextId;
}
/**
* Dig through a given dbObject for the nested value
*/
private Object getNestedValue(DBObject dbObject, String nestedKey)
{
String[] keyChain = nestedKey.split("\\.");
DBObject temp = dbObject;
for (int i = 0; i < keyChain.length - 1; ++i)
{
temp = (DBObject)temp.get(keyChain[i]);
if ( temp == null )
{
return null;
}
}
return temp.get(keyChain[keyChain.length - 1]);
}
}

View File

@ -0,0 +1,175 @@
package org.eclipse.jetty.mongodb;
//========================================================================
//Copyright (c) 2011 Intalio, Inc.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.server.session.AbstractSession;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
public class NoSqlSession extends AbstractSession
{
private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session");
private final NoSqlSessionManager _manager;
private Set<String> _dirty;
private final AtomicInteger _active = new AtomicInteger();
private Object _version;
private long _lastSync;
/* ------------------------------------------------------------ */
protected NoSqlSession(NoSqlSessionManager manager, long created, long accessed, String clusterId)
{
super(manager, created,accessed,clusterId);
_manager=manager;
save(true);
_active.incrementAndGet();
}
/* ------------------------------------------------------------ */
protected NoSqlSession(NoSqlSessionManager manager, long created, long accessed, String clusterId, Object version)
{
super(manager, created,accessed,clusterId);
_manager=manager;
_version=version;
}
/* ------------------------------------------------------------ */
@Override
protected Object doPutOrRemove(String name, Object value)
{
synchronized (this)
{
if (_dirty==null)
_dirty=new HashSet<String>();
_dirty.add(name);
Object old = super.doPutOrRemove(name,value);
if (_manager.getSavePeriod()==-2)
save(true);
return old;
}
}
/* ------------------------------------------------------------ */
@Override
protected void checkValid() throws IllegalStateException
{
super.checkValid();
}
/* ------------------------------------------------------------ */
@Override
protected boolean access(long time)
{
__log.debug("NoSqlSession:access:active "+_active);
if (_active.incrementAndGet()==1)
{
int period=_manager.getStalePeriod()*1000;
if (period==0)
refresh();
else if (period>0)
{
long stale=time-_lastSync;
__log.debug("NoSqlSession:access:stale "+stale);
if (stale>period)
refresh();
}
}
return super.access(time);
}
/* ------------------------------------------------------------ */
@Override
protected void complete()
{
super.complete();
if(_active.decrementAndGet()==0)
{
switch(_manager.getSavePeriod())
{
case 0:
save(isValid());
break;
case 1:
if (isDirty())
save(isValid());
break;
}
}
}
/* ------------------------------------------------------------ */
@Override
protected void doInvalidate() throws IllegalStateException
{
super.doInvalidate();
save(false);
}
/* ------------------------------------------------------------ */
protected void save(boolean activateAfterSave)
{
synchronized (this)
{
_version=_manager.save(this,_version,activateAfterSave);
_lastSync=getAccessed();
}
}
/* ------------------------------------------------------------ */
protected void refresh()
{
synchronized (this)
{
_version=_manager.refresh(this,_version);
}
}
/* ------------------------------------------------------------ */
public boolean isDirty()
{
synchronized (this)
{
return _dirty!=null && !_dirty.isEmpty();
}
}
/* ------------------------------------------------------------ */
public Set<String> takeDirty()
{
synchronized (this)
{
Set<String> dirty=_dirty;
if (dirty==null)
dirty= new HashSet<String>();
else
_dirty=null;
return dirty;
}
}
/* ------------------------------------------------------------ */
public Object getVersion()
{
return _version;
}
}

View File

@ -0,0 +1,317 @@
package org.eclipse.jetty.mongodb;
//========================================================================
//Copyright (c) 2011 Intalio, Inc.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.session.AbstractSession;
import org.eclipse.jetty.server.session.AbstractSessionManager;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public abstract class NoSqlSessionManager extends AbstractSessionManager implements SessionManager
{
private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session");
protected final ConcurrentMap<String,NoSqlSession> _sessions=new ConcurrentHashMap<String,NoSqlSession>();
private int _stalePeriod=0;
private int _savePeriod=0;
private int _idlePeriod=-1;
private boolean _invalidateOnStop;
private boolean _saveAllAttributes;
/* ------------------------------------------------------------ */
/* (non-Javadoc)
* @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
*/
@Override
public void doStart() throws Exception
{
super.doStart();
}
/* ------------------------------------------------------------ */
@Override
protected void addSession(AbstractSession session)
{
if (isRunning())
_sessions.put(session.getClusterId(),(NoSqlSession)session);
}
/* ------------------------------------------------------------ */
@Override
public AbstractSession getSession(String idInCluster)
{
NoSqlSession session = _sessions.get(idInCluster);
__log.debug("getSession: " + session );
if (session==null)
{
session=loadSession(idInCluster);
if (session!=null)
{
NoSqlSession race=_sessions.putIfAbsent(idInCluster,session);
if (race!=null)
{
session.willPassivate();
session.clearAttributes();
session=race;
}
}
}
return session;
}
/* ------------------------------------------------------------ */
@Override
protected void invalidateSessions() throws Exception
{
// Invalidate all sessions to cause unbind events
ArrayList<NoSqlSession> sessions=new ArrayList<NoSqlSession>(_sessions.values());
int loop=100;
while (sessions.size()>0 && loop-->0)
{
// If we are called from doStop
if (isStopping())
{
// Then we only save and remove the session - it is not invalidated.
for (NoSqlSession session : sessions)
{
session.save(false);
removeSession(session,false);
}
}
else
{
for (NoSqlSession session : sessions)
session.invalidate();
}
// check that no new sessions were created while we were iterating
sessions=new ArrayList<NoSqlSession>(_sessions.values());
}
}
/* ------------------------------------------------------------ */
@Override
protected AbstractSession newSession(HttpServletRequest request)
{
long created=System.currentTimeMillis();
String clusterId=getSessionIdManager().newSessionId(request,created);
return new NoSqlSession(this,created,created,clusterId);
}
/* ------------------------------------------------------------ */
@Override
protected boolean removeSession(String idInCluster)
{
synchronized (this)
{
NoSqlSession session = _sessions.remove(idInCluster);
try
{
if (session != null)
{
return remove(session);
}
}
catch (Exception e)
{
__log.warn("Problem deleting session id=" + idInCluster,e);
}
return session != null;
}
}
/* ------------------------------------------------------------ */
protected void invalidateSession( String idInCluster )
{
synchronized (this)
{
NoSqlSession session = _sessions.remove(idInCluster);
try
{
if (session != null)
{
remove(session);
}
}
catch (Exception e)
{
__log.warn("Problem deleting session id=" + idInCluster,e);
}
}
/*
* ought we not go to cluster and mark it invalid?
*/
}
/* ------------------------------------------------------------ */
/**
* The State Period is the maximum time in seconds that an in memory session is allows to be stale:
* <ul>
* <li>If this period is exceeded, the DB will be checked to see if a more recent version is available.</li>
* <li>If the state period is set to a value < 0, then no staleness check will be made.</li>
* <li>If the state period is set to 0, then a staleness check is made whenever the active request count goes from 0 to 1.</li>
* </ul>
* @return the stalePeriod in seconds
*/
public int getStalePeriod()
{
return _stalePeriod;
}
/* ------------------------------------------------------------ */
/**
* The State Period is the maximum time in seconds that an in memory session is allows to be stale:
* <ul>
* <li>If this period is exceeded, the DB will be checked to see if a more recent version is available.</li>
* <li>If the state period is set to a value < 0, then no staleness check will be made.</li>
* <li>If the state period is set to 0, then a staleness check is made whenever the active request count goes from 0 to 1.</li>
* </ul>
* @param stalePeriod the stalePeriod in seconds
*/
public void setStalePeriod(int stalePeriod)
{
_stalePeriod = stalePeriod;
}
/* ------------------------------------------------------------ */
/**
* The Save Period is the time in seconds between saves of a dirty session to the DB.
* When this period is exceeded, the a dirty session will be written to the DB: <ul>
* <li>a save period of -2 means the session is written to the DB whenever setAttribute is called.</li>
* <li>a save period of -1 means the session is never saved to the DB other than on a shutdown</li>
* <li>a save period of 0 means the session is written to the DB whenever the active request count goes from 1 to 0.</li>
* <li>a save period of 1 means the session is written to the DB whenever the active request count goes from 1 to 0 and the session is dirty.</li>
* <li>a save period of > 1 means the session is written after that period in seconds of being dirty.</li>
* </ul>
* @return the savePeriod -2,-1,0,1 or the period in seconds >=2
*/
public int getSavePeriod()
{
return _savePeriod;
}
/* ------------------------------------------------------------ */
/**
* The Save Period is the time in seconds between saves of a dirty session to the DB.
* When this period is exceeded, the a dirty session will be written to the DB: <ul>
* <li>a save period of -2 means the session is written to the DB whenever setAttribute is called.</li>
* <li>a save period of -1 means the session is never saved to the DB other than on a shutdown</li>
* <li>a save period of 0 means the session is written to the DB whenever the active request count goes from 1 to 0.</li>
* <li>a save period of 1 means the session is written to the DB whenever the active request count goes from 1 to 0 and the session is dirty.</li>
* <li>a save period of > 1 means the session is written after that period in seconds of being dirty.</li>
* </ul>
* @param savePeriod the savePeriod -2,-1,0,1 or the period in seconds >=2
*/
public void setSavePeriod(int savePeriod)
{
_savePeriod = savePeriod;
}
/* ------------------------------------------------------------ */
/**
* The Idle Period is the time in seconds before an in memory session is passivated.
* When this period is exceeded, the session will be passivated and removed from memory. If the session was dirty, it will be written to the DB.
* If the idle period is set to a value < 0, then the session is never idled.
* If the save period is set to 0, then the session is idled whenever the active request count goes from 1 to 0.
* @return the idlePeriod
*/
public int getIdlePeriod()
{
return _idlePeriod;
}
/* ------------------------------------------------------------ */
/**
* The Idle Period is the time in seconds before an in memory session is passivated.
* When this period is exceeded, the session will be passivated and removed from memory. If the session was dirty, it will be written to the DB.
* If the idle period is set to a value < 0, then the session is never idled.
* If the save period is set to 0, then the session is idled whenever the active request count goes from 1 to 0.
* @param idlePeriod the idlePeriod in seconds
*/
public void setIdlePeriod(int idlePeriod)
{
_idlePeriod = idlePeriod;
}
/* ------------------------------------------------------------ */
/**
* Invalidate sessions when the session manager is stopped otherwise save them to the DB.
* @return the invalidateOnStop
*/
public boolean isInvalidateOnStop()
{
return _invalidateOnStop;
}
/* ------------------------------------------------------------ */
/**
* Invalidate sessions when the session manager is stopped otherwise save them to the DB.
* @param invalidateOnStop the invalidateOnStop to set
*/
public void setInvalidateOnStop(boolean invalidateOnStop)
{
_invalidateOnStop = invalidateOnStop;
}
/* ------------------------------------------------------------ */
/**
* Save all attributes of a session or only update the dirty attributes.
* @return the saveAllAttributes
*/
public boolean isSaveAllAttributes()
{
return _saveAllAttributes;
}
/* ------------------------------------------------------------ */
/**
* Save all attributes of a session or only update the dirty attributes.
* @param saveAllAttributes the saveAllAttributes to set
*/
public void setSaveAllAttributes(boolean saveAllAttributes)
{
_saveAllAttributes = saveAllAttributes;
}
/* ------------------------------------------------------------ */
abstract protected NoSqlSession loadSession(String clusterId);
/* ------------------------------------------------------------ */
abstract protected Object save(NoSqlSession session,Object version, boolean activateAfterSave);
/* ------------------------------------------------------------ */
abstract protected Object refresh(NoSqlSession session, Object version);
/* ------------------------------------------------------------ */
abstract protected boolean remove(NoSqlSession session);
}

View File

@ -0,0 +1,45 @@
package org.eclipse.jetty.mongodb.jmx;
import org.eclipse.jetty.mongodb.MongoSessionManager;
import org.eclipse.jetty.server.handler.AbstractHandlerContainer;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.session.AbstractSessionManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.server.session.jmx.AbstractSessionManagerMBean;
public class MongoSessionManagerMBean extends AbstractSessionManagerMBean
{
public MongoSessionManagerMBean(Object managedObject)
{
super(managedObject);
}
/* ------------------------------------------------------------ */
public String getObjectContextBasis()
{
if (_managed != null && _managed instanceof MongoSessionManager)
{
MongoSessionManager manager = (MongoSessionManager)_managed;
String basis = null;
SessionHandler handler = manager.getSessionHandler();
if (handler != null)
{
ContextHandler context =
AbstractHandlerContainer.findContainerOf(handler.getServer(),
ContextHandler.class,
handler);
if (context != null)
basis = getContextName(context);
}
if (basis != null)
return basis;
}
return super.getObjectContextBasis();
}
}

View File

@ -0,0 +1,6 @@
MongoSessionManager: Mongo Session Manager
sessionStoreCount: total number of known sessions in the store
purge(): force a purge() of invalid sessions in the session store based on normal criteria
purgeFully(): force a full purge of invalid sessions in the session store
scavenge(): force a scavenge() of sessions known to this manager in the session store
scavengeFully(): force a scavenge of all sessions in the session store

View File

@ -0,0 +1,33 @@
package org.eclipse.jetty.mongodb;
// ========================================================================
// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
import org.eclipse.jetty.server.session.AbstractClientCrossContextSessionTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.junit.Test;
public class ClientCrossContextSessionTest extends AbstractClientCrossContextSessionTest
{
public AbstractTestServer createServer(int port)
{
return new MongoTestServer(port);
}
@Test
public void testCrossContextDispatch() throws Exception
{
super.testCrossContextDispatch();
}
}

View File

@ -0,0 +1,31 @@
package org.eclipse.jetty.mongodb;
//========================================================================
//Copyright (c) 2011 Intalio, Inc.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import org.eclipse.jetty.server.session.AbstractLastAccessTimeTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
public class LastAccessTimeTest extends AbstractLastAccessTimeTest
{
public AbstractTestServer createServer(int port, int max, int scavenge)
{
return new MongoTestServer(port,max,scavenge);
}
@Override
public void testLastAccessTime() throws Exception
{
super.testLastAccessTime();
}
}

View File

@ -0,0 +1,37 @@
package org.eclipse.jetty.mongodb;
// ========================================================================
// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
import org.eclipse.jetty.server.session.AbstractLightLoadTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.junit.Test;
/**
* LightLoadTest
*/
public class LightLoadTest extends AbstractLightLoadTest
{
public AbstractTestServer createServer(int port)
{
return new MongoTestServer(port);
}
@Test
public void testLightLoad() throws Exception
{
super.testLightLoad();
}
}

View File

@ -0,0 +1,60 @@
package org.eclipse.jetty.mongodb;
//========================================================================
//Copyright (c) 2011 Intalio, Inc.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.util.Set;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.WriteResult;
public class MongoTest
{
public static void main(String... args) throws Exception
{
Mongo m = new Mongo( "127.0.0.1" , 27017 );
DB db = m.getDB( "mydb" );
Set<String> colls = db.getCollectionNames();
System.err.println("Colls="+colls);
DBCollection coll = db.getCollection("testCollection");
BasicDBObject key = new BasicDBObject("id","1234");
BasicDBObject sets = new BasicDBObject("name","value");
BasicDBObject upsert=new BasicDBObject("$set",sets);
WriteResult result =coll.update(key,upsert,true,false);
System.err.println(result.getLastError());
while (coll.count()>0)
{
DBObject docZ = coll.findOne();
System.err.println("removing "+ docZ);
if (docZ!=null)
coll.remove(docZ);
}
}
}

View File

@ -0,0 +1,137 @@
package org.eclipse.jetty.mongodb;
// ========================================================================
// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.mongodb.MongoSessionIdManager;
import org.eclipse.jetty.mongodb.MongoSessionManager;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.eclipse.jetty.server.session.SessionHandler;
/**
* @version $Revision$ $Date$
*/
public class MongoTestServer extends AbstractTestServer
{
static MongoSessionIdManager _idManager;
private boolean _saveAllAttributes = false; // false save dirty, true save all
public MongoTestServer(int port)
{
super(port, 30, 10);
}
public MongoTestServer(int port, int maxInactivePeriod, int scavengePeriod)
{
super(port, maxInactivePeriod, scavengePeriod);
}
public MongoTestServer(int port, int maxInactivePeriod, int scavengePeriod, boolean saveAllAttributes)
{
super(port, maxInactivePeriod, scavengePeriod);
_saveAllAttributes = saveAllAttributes;
}
public SessionIdManager newSessionIdManager()
{
if ( _idManager != null )
{
try
{
_idManager.stop();
}
catch (Exception e)
{
e.printStackTrace();
}
_idManager.setScavengeDelay(_scavengePeriod + 1000);
_idManager.setScavengePeriod(_maxInactivePeriod);
try
{
_idManager.start();
}
catch (Exception e)
{
e.printStackTrace();
}
return _idManager;
}
try
{
System.err.println("MongoTestServer:SessionIdManager:" + _maxInactivePeriod + "/" + _scavengePeriod);
_idManager = new MongoSessionIdManager(_server);
_idManager.setScavengeDelay((int)TimeUnit.SECONDS.toMillis(_scavengePeriod));
_idManager.setScavengePeriod(_maxInactivePeriod);
return _idManager;
}
catch (Exception e)
{
throw new IllegalStateException();
}
}
public SessionManager newSessionManager()
{
MongoSessionManager manager;
try
{
manager = new MongoSessionManager();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
manager.setSavePeriod(1);
manager.setStalePeriod(0);
manager.setSaveAllAttributes(_saveAllAttributes);
//manager.setScavengePeriod((int)TimeUnit.SECONDS.toMillis(_scavengePeriod));
return manager;
}
public SessionHandler newSessionHandler(SessionManager sessionManager)
{
return new SessionHandler(sessionManager);
}
public static void main(String... args) throws Exception
{
MongoTestServer server8080 = new MongoTestServer(8080);
server8080.addContext("/").addServlet(SessionDump.class,"/");
server8080.start();
MongoTestServer server8081 = new MongoTestServer(8081);
server8081.addContext("/").addServlet(SessionDump.class,"/");
server8081.start();
server8080.join();
server8081.join();
}
}

View File

@ -0,0 +1,36 @@
package org.eclipse.jetty.mongodb;
// ========================================================================
// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
import org.eclipse.jetty.server.session.AbstractNewSessionTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.junit.Test;
/**
* NewSessionTest
*/
public class NewSessionTest extends AbstractNewSessionTest
{
public AbstractTestServer createServer(int port, int max, int scavenge)
{
return new MongoTestServer(port,max,scavenge);
}
@Test
public void testNewSession() throws Exception
{
super.testNewSession();
}
}

View File

@ -0,0 +1,35 @@
package org.eclipse.jetty.mongodb;
// ========================================================================
// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
import org.eclipse.jetty.server.session.AbstractOrphanedSessionTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.junit.Test;
/**
* OrphanedSessionTest
*/
public class OrphanedSessionTest extends AbstractOrphanedSessionTest
{
public AbstractTestServer createServer(int port, int max, int scavenge)
{
return new MongoTestServer(port,max,scavenge);
}
@Test
public void testOrphanedSession() throws Exception
{
super.testOrphanedSession();
}
}

View File

@ -0,0 +1,36 @@
package org.eclipse.jetty.mongodb;
// ========================================================================
// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
import org.eclipse.jetty.server.session.AbstractReentrantRequestSessionTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.junit.Test;
/**
* ReentrantRequestSessionTest
*/
public class ReentrantRequestSessionTest extends AbstractReentrantRequestSessionTest
{
public AbstractTestServer createServer(int port)
{
return new MongoTestServer(port);
}
@Test
public void testReentrantRequestSession() throws Exception
{
super.testReentrantRequestSession();
}
}

View File

@ -0,0 +1,33 @@
package org.eclipse.jetty.mongodb;
//========================================================================
//Copyright (c) 2011 Intalio, Inc.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import org.eclipse.jetty.server.session.AbstractRemoveSessionTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.junit.Test;
public class RemoveSessionTest extends AbstractRemoveSessionTest
{
public AbstractTestServer createServer(int port, int max, int scavenge)
{
return new MongoTestServer(port,max,scavenge);
}
@Test
public void testRemoveSession() throws Exception
{
super.testRemoveSession();
}
}

View File

@ -0,0 +1,32 @@
package org.eclipse.jetty.mongodb;
// ========================================================================
// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
import org.eclipse.jetty.server.session.AbstractServerCrossContextSessionTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.junit.Test;
public class ServerCrossContextSessionTest extends AbstractServerCrossContextSessionTest
{
public AbstractTestServer createServer(int port)
{
return new MongoTestServer(port);
}
@Test
public void testCrossContextDispatch() throws Exception
{
super.testCrossContextDispatch();
}
}

View File

@ -0,0 +1,181 @@
package org.eclipse.jetty.mongodb;
// ========================================================================
// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/* ------------------------------------------------------------ */
/** Test Servlet Sessions.
*
*
*/
public class SessionDump extends HttpServlet
{
int redirectCount=0;
/* ------------------------------------------------------------ */
String pageType;
/* ------------------------------------------------------------ */
@Override
public void init(ServletConfig config)
throws ServletException
{
super.init(config);
}
/* ------------------------------------------------------------ */
protected void handleForm(HttpServletRequest request,
HttpServletResponse response)
{
HttpSession session = request.getSession(false);
String action = request.getParameter("Action");
String name = request.getParameter("Name");
String value = request.getParameter("Value");
if (action!=null)
{
if(action.equals("New Session"))
{
session = request.getSession(true);
session.setAttribute("test","value");
}
else if (session!=null)
{
if (action.equals("Invalidate"))
session.invalidate();
else if (action.equals("Set") && name!=null && name.length()>0)
session.setAttribute(name,value);
else if (action.equals("Remove"))
session.removeAttribute(name);
}
}
}
/* ------------------------------------------------------------ */
@Override
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
handleForm(request,response);
String nextUrl = getURI(request)+"?R="+redirectCount++;
String encodedUrl=response.encodeRedirectURL(nextUrl);
response.sendRedirect(encodedUrl);
}
/* ------------------------------------------------------------ */
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
handleForm(request,response);
response.setContentType("text/html");
HttpSession session = request.getSession(getURI(request).indexOf("new")>0);
try
{
if (session!=null)
session.isNew();
}
catch(IllegalStateException e)
{
session=null;
}
PrintWriter out = response.getWriter();
out.println("<h1>Session Dump Servlet:</h1>");
out.println("<form action=\""+response.encodeURL(getURI(request))+"\" method=\"post\">");
if (session==null)
{
out.println("<H3>No Session</H3>");
out.println("<input type=\"submit\" name=\"Action\" value=\"New Session\"/>");
}
else
{
try
{
out.println("<b>ID:</b> "+session.getId()+"<br/>");
out.println("<b>New:</b> "+session.isNew()+"<br/>");
out.println("<b>Created:</b> "+new Date(session.getCreationTime())+"<br/>");
out.println("<b>Last:</b> "+new Date(session.getLastAccessedTime())+"<br/>");
out.println("<b>Max Inactive:</b> "+session.getMaxInactiveInterval()+"<br/>");
out.println("<b>Context:</b> "+session.getServletContext()+"<br/>");
Enumeration keys=session.getAttributeNames();
while(keys.hasMoreElements())
{
String name=(String)keys.nextElement();
String value=""+session.getAttribute(name);
out.println("<b>"+name+":</b> "+value+"<br/>");
}
out.println("<b>Name:</b><input type=\"text\" name=\"Name\" /><br/>");
out.println("<b>Value:</b><input type=\"text\" name=\"Value\" /><br/>");
out.println("<input type=\"submit\" name=\"Action\" value=\"Set\"/>");
out.println("<input type=\"submit\" name=\"Action\" value=\"Remove\"/>");
out.println("<input type=\"submit\" name=\"Action\" value=\"Refresh\"/>");
out.println("<input type=\"submit\" name=\"Action\" value=\"Invalidate\"/><br/>");
out.println("</form><br/>");
if (request.isRequestedSessionIdFromCookie())
out.println("<P>Turn off cookies in your browser to try url encoding<BR>");
if (request.isRequestedSessionIdFromURL())
out.println("<P>Turn on cookies in your browser to try cookie encoding<BR>");
out.println("<a href=\""+response.encodeURL(request.getRequestURI()+"?q=0")+"\">Encoded Link</a><BR>");
}
catch (IllegalStateException e)
{
e.printStackTrace();
}
}
}
/* ------------------------------------------------------------ */
@Override
public String getServletInfo() {
return "Session Dump Servlet";
}
/* ------------------------------------------------------------ */
private String getURI(HttpServletRequest request)
{
String uri=(String)request.getAttribute("javax.servlet.forward.request_uri");
if (uri==null)
uri=request.getRequestURI();
return uri;
}
}

View File

@ -0,0 +1,244 @@
package org.eclipse.jetty.mongodb;
//========================================================================
//Copyright (c) 2011 Intalio, Inc.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//The Eclipse Public License is available at
//http://www.eclipse.org/legal/epl-v10.html
//The Apache License v2.0 is available at
//http://www.opensource.org/licenses/apache2.0.php
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import javax.management.remote.JMXServiceURL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.jmx.ConnectorServer;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.mongodb.NoSqlSession;
import org.eclipse.jetty.server.session.AbstractSessionValueSavingTest;
import org.eclipse.jetty.server.session.AbstractTestServer;
import org.junit.Test;
public class SessionSavingValueTest extends AbstractSessionValueSavingTest
{
public AbstractTestServer createServer(int port, int max, int scavenge)
{
// ConnectorServer srv = null;
try
{
// srv = new ConnectorServer(
// new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:0/jettytest"),
// "org.eclipse.jetty:name=rmiconnectorserver");
// srv.start();
MongoTestServer server = new MongoTestServer(port,max,scavenge,true);
// MBeanContainer mbean = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
//
// server.getServer().getContainer().addEventListener(mbean);
// server.getServer().addBean(mbean);
//
// mbean.start();
return server;
}
// catch (MalformedURLException e)
// {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
@Test
public void testSessionValueSaving() throws Exception
{
String contextPath = "";
String servletMapping = "/server";
int maxInactivePeriod = 10000;
int scavengePeriod = 20000;
AbstractTestServer server1 = createServer(0,maxInactivePeriod,scavengePeriod);
server1.addContext(contextPath).addServlet(TestServlet.class,servletMapping);
server1.start();
int port1 = server1.getPort();
try
{
HttpClient client = new HttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
client.start();
try
{
String[] sessionTestValue = new String[]
{ "0", "null" };
// Perform one request to server1 to create a session
ContentExchange exchange1 = new ContentExchange(true);
exchange1.setMethod(HttpMethods.GET);
exchange1.setURL("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
client.send(exchange1);
exchange1.waitForDone();
assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
String[] sessionTestResponse = exchange1.getResponseContent().split("/");
assertTrue(Long.parseLong(sessionTestValue[0]) < Long.parseLong(sessionTestResponse[0]));
sessionTestValue = sessionTestResponse;
String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
assertTrue(sessionCookie != null);
// Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=","$1\\$Path=");
// Perform some request to server2 using the session cookie from the previous request
// This should migrate the session from server1 to server2, and leave server1's
// session in a very stale state, while server2 has a very fresh session.
// We want to test that optimizations done to the saving of the shared lastAccessTime
// do not break the correct working
int requestInterval = 500;
for (int i = 0; i < 10; ++i)
{
ContentExchange exchange2 = new ContentExchange(true);
exchange2.setMethod(HttpMethods.GET);
exchange2.setURL("http://localhost:" + port1 + contextPath + servletMapping);
exchange2.getRequestFields().add("Cookie",sessionCookie);
client.send(exchange2);
exchange2.waitForDone();
assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
sessionTestResponse = exchange2.getResponseContent().split("/");
assertTrue(Long.parseLong(sessionTestValue[0]) < Long.parseLong(sessionTestResponse[0]));
assertTrue(Long.parseLong(sessionTestValue[1]) < Long.parseLong(sessionTestResponse[1]));
sessionTestValue = sessionTestResponse;
String setCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
if (setCookie != null)
sessionCookie = setCookie.replaceFirst("(\\W)(P|p)ath=","$1\\$Path=");
Thread.sleep(requestInterval);
}
// Thread.sleep(320000);
}
finally
{
client.stop();
}
}
finally
{
server1.stop();
}
}
public static class TestServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
{
String action = request.getParameter("action");
if ("init".equals(action))
{
NoSqlSession session = (NoSqlSession)request.getSession(true);
session.setAttribute("test",System.currentTimeMillis());
session.setAttribute("objectTest", new Pojo("foo","bar"));
sendResult(session,httpServletResponse.getWriter());
}
else
{
NoSqlSession session = (NoSqlSession)request.getSession(false);
if (session != null)
{
long value = System.currentTimeMillis();
session.setAttribute("test",value);
}
sendResult(session,httpServletResponse.getWriter());
Pojo p = (Pojo)session.getAttribute("objectTest");
//System.out.println(p.getName() + " / " + p.getValue() );
}
}
private void sendResult(NoSqlSession session, PrintWriter writer)
{
if (session != null)
{
if (session.getVersion() == null)
{
writer.print(session.getAttribute("test") + "/-1");
}
else
{
writer.print(session.getAttribute("test") + "/" + session.getVersion());
}
}
else
{
writer.print("0/-1");
}
}
public class Pojo implements Serializable
{
private String _name;
private String _value;
public Pojo( String name, String value )
{
_name = name;
_value = value;
}
public String getName()
{
return _name;
}
public String getValue()
{
return _value;
}
}
}
}

View File

@ -319,6 +319,7 @@
<module>jetty-start</module>
<module>jetty-nested</module>
<module>jetty-overlay-deployer</module>
<module>jetty-nosql</module>
<module>test-continuation</module>
<module>test-continuation-jetty6</module>
<module>test-jetty-servlet</module>