From 2ead7a83fe013a2b6e5390b7451ea70439a37414 Mon Sep 17 00:00:00 2001 From: Jesse McConnell Date: Tue, 26 Jul 2011 15:00:29 -0500 Subject: [PATCH] [Bug 351516] support nosql sessions --- jetty-nosql/pom.xml | 94 +++ .../jetty/mongodb/MongoSessionIdManager.java | 540 +++++++++++++++++ .../jetty/mongodb/MongoSessionManager.java | 547 ++++++++++++++++++ .../eclipse/jetty/mongodb/NoSqlSession.java | 175 ++++++ .../jetty/mongodb/NoSqlSessionManager.java | 317 ++++++++++ .../mongodb/jmx/MongoSessionManagerMBean.java | 45 ++ .../jmx/MongoSessionManager-mbean.properties | 6 + .../ClientCrossContextSessionTest.java | 33 ++ .../jetty/mongodb/LastAccessTimeTest.java | 31 + .../eclipse/jetty/mongodb/LightLoadTest.java | 37 ++ .../org/eclipse/jetty/mongodb/MongoTest.java | 60 ++ .../jetty/mongodb/MongoTestServer.java | 137 +++++ .../eclipse/jetty/mongodb/NewSessionTest.java | 36 ++ .../jetty/mongodb/OrphanedSessionTest.java | 35 ++ .../mongodb/ReentrantRequestSessionTest.java | 36 ++ .../jetty/mongodb/RemoveSessionTest.java | 33 ++ .../ServerCrossContextSessionTest.java | 32 + .../eclipse/jetty/mongodb/SessionDump.java | 181 ++++++ .../jetty/mongodb/SessionSavingValueTest.java | 244 ++++++++ pom.xml | 1 + 20 files changed, 2620 insertions(+) create mode 100644 jetty-nosql/pom.xml create mode 100644 jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/MongoSessionIdManager.java create mode 100644 jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/MongoSessionManager.java create mode 100644 jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/NoSqlSession.java create mode 100644 jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/NoSqlSessionManager.java create mode 100644 jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/jmx/MongoSessionManagerMBean.java create mode 100644 jetty-nosql/src/main/resources/org/eclipse/jetty/mongodb/jmx/MongoSessionManager-mbean.properties create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ClientCrossContextSessionTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/LastAccessTimeTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/LightLoadTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/MongoTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/MongoTestServer.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/NewSessionTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/OrphanedSessionTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ReentrantRequestSessionTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/RemoveSessionTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ServerCrossContextSessionTest.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/SessionDump.java create mode 100644 jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/SessionSavingValueTest.java diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml new file mode 100644 index 00000000000..e53a341a6df --- /dev/null +++ b/jetty-nosql/pom.xml @@ -0,0 +1,94 @@ + + + org.eclipse.jetty + jetty-project + 7.5.0-SNAPSHOT + + 4.0.0 + jetty-nosql + Jetty :: NoSQL Session Managers + + ${project.version} + ${junit-version} + ${project.groupId}.mongodb + + install + + + maven-compiler-plugin + + 1.6 + 1.6 + false + + + + org.apache.felix + maven-bundle-plugin + + + 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)",* + + + true + + + + manifest + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + artifact-jar + + jar + + + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + + + org.eclipse.jetty + jetty-server + ${project/version} + + + junit + junit + ${junit4-version} + test + + + org.eclipse.jetty + jetty-jmx + ${project.version} + true + + + org.mongodb + mongo-java-driver + 2.6.1 + jar + compile + + + org.eclipse.jetty.tests + test-sessions-common + ${jetty-version} + test + + + diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/MongoSessionIdManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/MongoSessionIdManager.java new file mode 100644 index 00000000000..79d47a95ee8 --- /dev/null +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/MongoSessionIdManager.java @@ -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 _sessionsIds = new HashSet(); + + + /* ------------------------------------------------------------ */ + 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 && i0)?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; + } + +} diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/MongoSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/MongoSessionManager.java new file mode 100644 index 00000000000..fe66e0b7667 --- /dev/null +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/MongoSessionManager.java @@ -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 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 map = new HashMap(); + 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]); + } + +} diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/NoSqlSession.java b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/NoSqlSession.java new file mode 100644 index 00000000000..5510de297c6 --- /dev/null +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/NoSqlSession.java @@ -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 _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(); + _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 takeDirty() + { + synchronized (this) + { + Set dirty=_dirty; + if (dirty==null) + dirty= new HashSet(); + else + _dirty=null; + return dirty; + } + } + + /* ------------------------------------------------------------ */ + public Object getVersion() + { + return _version; + } + +} diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/NoSqlSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/NoSqlSessionManager.java new file mode 100644 index 00000000000..180a0726218 --- /dev/null +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/NoSqlSessionManager.java @@ -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 _sessions=new ConcurrentHashMap(); + + 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 sessions=new ArrayList(_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(_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: + *
    + *
  • If this period is exceeded, the DB will be checked to see if a more recent version is available.
  • + *
  • If the state period is set to a value < 0, then no staleness check will be made.
  • + *
  • If the state period is set to 0, then a staleness check is made whenever the active request count goes from 0 to 1.
  • + *
+ * @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: + *
    + *
  • If this period is exceeded, the DB will be checked to see if a more recent version is available.
  • + *
  • If the state period is set to a value < 0, then no staleness check will be made.
  • + *
  • If the state period is set to 0, then a staleness check is made whenever the active request count goes from 0 to 1.
  • + *
+ * @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:
    + *
  • a save period of -2 means the session is written to the DB whenever setAttribute is called.
  • + *
  • a save period of -1 means the session is never saved to the DB other than on a shutdown
  • + *
  • a save period of 0 means the session is written to the DB whenever the active request count goes from 1 to 0.
  • + *
  • 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.
  • + *
  • a save period of > 1 means the session is written after that period in seconds of being dirty.
  • + *
+ * @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:
    + *
  • a save period of -2 means the session is written to the DB whenever setAttribute is called.
  • + *
  • a save period of -1 means the session is never saved to the DB other than on a shutdown
  • + *
  • a save period of 0 means the session is written to the DB whenever the active request count goes from 1 to 0.
  • + *
  • 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.
  • + *
  • a save period of > 1 means the session is written after that period in seconds of being dirty.
  • + *
+ * @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); + +} diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/jmx/MongoSessionManagerMBean.java b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/jmx/MongoSessionManagerMBean.java new file mode 100644 index 00000000000..cfc83ed2c58 --- /dev/null +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/mongodb/jmx/MongoSessionManagerMBean.java @@ -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(); + } + + + +} diff --git a/jetty-nosql/src/main/resources/org/eclipse/jetty/mongodb/jmx/MongoSessionManager-mbean.properties b/jetty-nosql/src/main/resources/org/eclipse/jetty/mongodb/jmx/MongoSessionManager-mbean.properties new file mode 100644 index 00000000000..dbce43a0ba1 --- /dev/null +++ b/jetty-nosql/src/main/resources/org/eclipse/jetty/mongodb/jmx/MongoSessionManager-mbean.properties @@ -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 diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ClientCrossContextSessionTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ClientCrossContextSessionTest.java new file mode 100644 index 00000000000..98b0bb4a4e5 --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ClientCrossContextSessionTest.java @@ -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(); + } + +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/LastAccessTimeTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/LastAccessTimeTest.java new file mode 100644 index 00000000000..c3fa2bbe783 --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/LastAccessTimeTest.java @@ -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(); + } +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/LightLoadTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/LightLoadTest.java new file mode 100644 index 00000000000..98abc591e41 --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/LightLoadTest.java @@ -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(); + } + +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/MongoTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/MongoTest.java new file mode 100644 index 00000000000..437507776cb --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/MongoTest.java @@ -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 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); + } + + + } +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/MongoTestServer.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/MongoTestServer.java new file mode 100644 index 00000000000..cc9c47d6d24 --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/MongoTestServer.java @@ -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(); + } + +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/NewSessionTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/NewSessionTest.java new file mode 100644 index 00000000000..15cb63e45ed --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/NewSessionTest.java @@ -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(); + } +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/OrphanedSessionTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/OrphanedSessionTest.java new file mode 100644 index 00000000000..b5176e9372a --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/OrphanedSessionTest.java @@ -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(); + } +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ReentrantRequestSessionTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ReentrantRequestSessionTest.java new file mode 100644 index 00000000000..4196cee7c6e --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ReentrantRequestSessionTest.java @@ -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(); + } + +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/RemoveSessionTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/RemoveSessionTest.java new file mode 100644 index 00000000000..64c26652522 --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/RemoveSessionTest.java @@ -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(); + } + +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ServerCrossContextSessionTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ServerCrossContextSessionTest.java new file mode 100644 index 00000000000..a9892a475bd --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/ServerCrossContextSessionTest.java @@ -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(); + } +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/SessionDump.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/SessionDump.java new file mode 100644 index 00000000000..fb313c79b5f --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/SessionDump.java @@ -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("

Session Dump Servlet:

"); + out.println("
"); + + if (session==null) + { + out.println("

No Session

"); + out.println(""); + } + else + { + try + { + out.println("ID: "+session.getId()+"
"); + out.println("New: "+session.isNew()+"
"); + out.println("Created: "+new Date(session.getCreationTime())+"
"); + out.println("Last: "+new Date(session.getLastAccessedTime())+"
"); + out.println("Max Inactive: "+session.getMaxInactiveInterval()+"
"); + out.println("Context: "+session.getServletContext()+"
"); + + + Enumeration keys=session.getAttributeNames(); + while(keys.hasMoreElements()) + { + String name=(String)keys.nextElement(); + String value=""+session.getAttribute(name); + + out.println(""+name+": "+value+"
"); + } + + out.println("Name:
"); + out.println("Value:
"); + + out.println(""); + out.println(""); + out.println(""); + out.println("
"); + + out.println("

"); + + if (request.isRequestedSessionIdFromCookie()) + out.println("

Turn off cookies in your browser to try url encoding
"); + + if (request.isRequestedSessionIdFromURL()) + out.println("

Turn on cookies in your browser to try cookie encoding
"); + out.println("Encoded Link
"); + + } + 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; + } + +} diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/SessionSavingValueTest.java b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/SessionSavingValueTest.java new file mode 100644 index 00000000000..431f900608b --- /dev/null +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/mongodb/SessionSavingValueTest.java @@ -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; + } + } + + } + + +} diff --git a/pom.xml b/pom.xml index 7a7224c843f..93337e04b15 100644 --- a/pom.xml +++ b/pom.xml @@ -319,6 +319,7 @@ jetty-start jetty-nested jetty-overlay-deployer + jetty-nosql test-continuation test-continuation-jetty6 test-jetty-servlet