diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AbstractSessionDataStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AbstractSessionDataStore.java index e8a1813615a..8896ad20c12 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AbstractSessionDataStore.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AbstractSessionDataStore.java @@ -46,6 +46,4 @@ public abstract class AbstractSessionDataStore extends AbstractLifeCycle impleme data.setDirty(false); } } - - } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AbstractSessionStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AbstractSessionStore.java index fed5ef7a619..6e2dfe561d2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AbstractSessionStore.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AbstractSessionStore.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.util.component.AbstractLifeCycle; public abstract class AbstractSessionStore extends AbstractLifeCycle implements SessionStore { protected SessionDataStore _sessionDataStore; + protected StalenessStrategy _staleStrategy; @@ -78,6 +79,16 @@ public abstract class AbstractSessionStore extends AbstractLifeCycle implements _sessionDataStore = sessionDataStore; } + public StalenessStrategy getStaleStrategy() + { + return _staleStrategy; + } + + public void setStaleStrategy(StalenessStrategy staleStrategy) + { + _staleStrategy = staleStrategy; + } + /** * Get a session object. * @@ -161,7 +172,6 @@ public abstract class AbstractSessionStore extends AbstractLifeCycle implements public boolean delete(SessionKey key) throws Exception { boolean deleted = true; - //TODO synchronization??? if (_sessionDataStore != null) deleted = _sessionDataStore.delete(key); doDelete(key); @@ -170,7 +180,21 @@ public abstract class AbstractSessionStore extends AbstractLifeCycle implements public boolean isStale (Session session) { - //TODO implement (pluggable?) algorithm for deciding if memory is stale + if (_staleStrategy != null) + return _staleStrategy.isStale(session); return false; } + + + + /** + * @see org.eclipse.jetty.server.session.x.SessionStore#scavenge() + */ + @Override + public void scavenge() + { + if (!isStarted()) + return; + _sessionDataStore.scavenge(); + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AlwaysStaleStrategy.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AlwaysStaleStrategy.java new file mode 100644 index 00000000000..90eeb0fb28d --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/AlwaysStaleStrategy.java @@ -0,0 +1,39 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session.x; + +/** + * AlwaysStale + * + * + */ +public class AlwaysStaleStrategy implements StalenessStrategy +{ + + /** + * @see org.eclipse.jetty.server.session.x.StalenessStrategy#isStale(org.eclipse.jetty.server.session.x.Session) + */ + @Override + public boolean isStale(Session session) + { + return true; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/FileSessionDataStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/FileSessionDataStore.java index 5ca66737b81..aadba517930 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/FileSessionDataStore.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/FileSessionDataStore.java @@ -29,7 +29,9 @@ import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.eclipse.jetty.util.ClassLoadingObjectInputStream; import org.eclipse.jetty.util.log.Log; @@ -182,13 +184,15 @@ public class FileSessionDataStore extends AbstractSessionDataStore if (size>0) { // input stream should not be closed here + Map attributes = new HashMap(); ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is); for (int i=0; i attributes) - { - _attributes.putAll(attributes); - } - - //TODO immutable?? - public Map getAttributes () - { - return _attributes; - } - } - - - - - - /** - * - */ + + public JDBCSessionDataStore () { super (); @@ -826,7 +773,7 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore @Override public SessionData newSessionData(String id, long created, long accessed, long lastAccessed, long maxInactiveMs) { - return new JDBCSessionData(id, created, accessed, lastAccessed, maxInactiveMs); + return new SessionData(id, created, accessed, lastAccessed, maxInactiveMs); } @@ -881,15 +828,14 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore PreparedStatement statement = _sessionTableSchema.getLoadStatement(connection, key); ResultSet result = statement.executeQuery()) { - JDBCSessionData data = null; + SessionData data = null; if (result.next()) { - data = (JDBCSessionData)newSessionData(key.getId(), - result.getLong(_sessionTableSchema.getCreateTimeColumn()), - result.getLong(_sessionTableSchema.getAccessTimeColumn()), - result.getLong(_sessionTableSchema.getLastAccessTimeColumn()), - result.getLong(_sessionTableSchema.getMaxIntervalColumn())); - data.setRowId(result.getString(_sessionTableSchema.getRowIdColumn())); + data = newSessionData(key.getId(), + result.getLong(_sessionTableSchema.getCreateTimeColumn()), + result.getLong(_sessionTableSchema.getAccessTimeColumn()), + result.getLong(_sessionTableSchema.getLastAccessTimeColumn()), + result.getLong(_sessionTableSchema.getMaxIntervalColumn())); data.setCookieSet(result.getLong(_sessionTableSchema.getCookieTimeColumn())); data.setLastNode(result.getString(_sessionTableSchema.getLastNodeColumn())); data.setLastSaved(result.getLong(_sessionTableSchema.getLastSavedTimeColumn())); @@ -901,7 +847,7 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is)) { Object o = ois.readObject(); - data.setAttributes((Map)o); + data.putAllAttributes((Map)o); } catch (Exception e) { @@ -946,41 +892,109 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore @Override public void doStore(SessionKey key, SessionData data) throws Exception { - // TODO write session data to jdbc if (data==null || key==null) return; - try (Connection connection = _dbAdaptor.getConnection(); - PreparedStatement statement = connection.prepareStatement(_sessionTableSchema.getUpdateSessionStatementAsString())) + try (Connection connection = _dbAdaptor.getConnection()) { - long now = System.currentTimeMillis(); connection.setAutoCommit(true); - statement.setString(1, key.getId()); - statement.setString(2, data.getLastNode());//should be my node id - statement.setLong(3, data.getAccessed());//accessTime - statement.setLong(4, data.getLastAccessed()); //lastAccessTime - statement.setLong(5, now); //last saved time - statement.setLong(6, data.getExpiry()); - statement.setLong(7, data.getMaxInactiveMs()); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(((JDBCSessionData)data).getAttributes()); - oos.flush(); - byte[] bytes = baos.toByteArray(); - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - - statement.setBinaryStream(8, bais, bytes.length);//attribute map as blob - statement.setString(9, ((JDBCSessionData)data).getRowId()); //rowId - statement.executeUpdate(); - - ((JDBCSessionData)data).setLastSaved(now); + + //If last saved field not set, then this is a fresh session that has never been persisted + if (data.getLastSaved() <= 0) + { + doInsert(connection, key, data); + } + else + { + doUpdate(connection, key, data); + } + } - if (LOG.isDebugEnabled()) - LOG.debug("Updated session "+data); + } + private void doInsert (Connection connection, SessionKey key, SessionData data) + throws Exception + { + String s = _sessionTableSchema.getInsertSessionStatementAsString(); + + try (PreparedStatement statement = connection.prepareStatement(s)) + { + + long now = System.currentTimeMillis(); + + + statement.setString(1, key.getId()); //session id + statement.setString(2, key.getCanonicalContextPath()); //context path + statement.setString(3, key.getVhost()); //first vhost + statement.setString(4, data.getLastNode());//my node id + statement.setLong(5, data.getAccessed());//accessTime + statement.setLong(6, data.getLastAccessed()); //lastAccessTime + statement.setLong(7, data.getCreated()); //time created + statement.setLong(8, data.getCookieSet());//time cookie was set + statement.setLong(9, now); //last saved time + statement.setLong(10, data.getExpiry()); + statement.setLong(11, data.getMaxInactiveMs()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(data.getAllAttributes()); + oos.flush(); + byte[] bytes = baos.toByteArray(); + + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob + statement.executeUpdate(); + data.setLastSaved(now); + if (LOG.isDebugEnabled()) + LOG.debug("Inserted session "+data); + } + } + + private void doUpdate (Connection connection, SessionKey key, SessionData data) + throws Exception + { + try (PreparedStatement statement = connection.prepareStatement(_sessionTableSchema.getUpdateSessionStatementAsString(key))) + { + + long now = System.currentTimeMillis(); + + + statement.setString(1, data.getLastNode());//should be my node id + statement.setLong(2, data.getAccessed());//accessTime + statement.setLong(3, data.getLastAccessed()); //lastAccessTime + statement.setLong(4, now); //last saved time + statement.setLong(5, data.getExpiry()); + statement.setLong(6, data.getMaxInactiveMs()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(data.getAllAttributes()); + oos.flush(); + byte[] bytes = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + statement.setBinaryStream(7, bais, bytes.length);//attribute map as blob + + if ((key.getCanonicalContextPath() == null || "".equals(key.getCanonicalContextPath())) && _dbAdaptor.isEmptyStringNull()) + { + statement.setString(8, key.getId()); + statement.setString(9, key.getVhost()); + } + else + { + statement.setString(8, key.getId()); + statement.setString(9, key.getCanonicalContextPath()); + statement.setString(10, key.getVhost()); + } + + statement.executeUpdate(); + + data.setLastSaved(now); + if (LOG.isDebugEnabled()) + LOG.debug("Updated session "+data); + } + } /** @@ -989,8 +1003,25 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore @Override public void scavenge() { - // TODO Auto-generated method stub + if (LOG.isDebugEnabled()) + LOG.debug("Scavenge sweep started at "+System.currentTimeMillis()); + + long now = System.currentTimeMillis(); + //first time we're called, don't scavenge + if (_lastScavengeTime == 0) + { + _lastScavengeTime = now; + return; + } + + + /*TODO + * 1. Select sessions for our node and our context that have expired since our last pass, giving some leeway + * 2. Select sessions for our node that have expired some time ago + * 3. Select sessions for any node that have expired quite a while ago + */ + } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/MemorySessionStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/MemorySessionStore.java index 9e2acb2176c..be6b36213b4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/MemorySessionStore.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/MemorySessionStore.java @@ -89,7 +89,7 @@ public class MemorySessionStore extends AbstractSessionStore if (isStale(session)) { - //delete from memory + //delete from memory so should reload doDelete(key); return null; } @@ -193,16 +193,4 @@ public class MemorySessionStore extends AbstractSessionStore } - - - /** - * @see org.eclipse.jetty.server.session.x.SessionStore#scavenge() - */ - @Override - public void scavenge() - { - // TODO Auto-generated method stub - - } - } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/NeverStaleStrategy.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/NeverStaleStrategy.java new file mode 100644 index 00000000000..71dc964e78d --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/NeverStaleStrategy.java @@ -0,0 +1,39 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session.x; + +/** + * NeverStale + * + * + */ +public class NeverStaleStrategy implements StalenessStrategy +{ + + /** + * @see org.eclipse.jetty.server.session.x.StalenessStrategy#isStale(org.eclipse.jetty.server.session.x.Session) + */ + @Override + public boolean isStale(Session session) + { + return false; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/SessionData.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/SessionData.java index 1f1ee516b6e..5ca38522404 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/SessionData.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/SessionData.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.server.session.x; import java.io.IOException; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -33,6 +34,9 @@ import java.util.concurrent.ConcurrentHashMap; */ public class SessionData implements Serializable { + + private static final long serialVersionUID = 1L; + protected String _id; protected String _contextPath; @@ -50,7 +54,7 @@ public class SessionData implements Serializable protected long _maxInactiveMs; protected Map _attributes = new ConcurrentHashMap(); protected boolean _dirty; - + protected long _lastSaved; //time in msec since last save public SessionData (String id, long created, long accessed, long lastAccessed, long maxInactiveMs) @@ -61,6 +65,20 @@ public class SessionData implements Serializable _lastAccessed = lastAccessed; _maxInactiveMs = maxInactiveMs; } + + + public long getLastSaved() + { + return _lastSaved; + } + + + + public void setLastSaved(long lastSaved) + { + _lastSaved = lastSaved; + } + public boolean isDirty() { @@ -93,6 +111,16 @@ public class SessionData implements Serializable } + public void putAllAttributes (Map attributes) + { + _attributes.putAll(attributes); + } + + public Map getAllAttributes() + { + return Collections.unmodifiableMap(_attributes); + } + public String getId() { return _id; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/SessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/SessionManager.java index a101f63c9fc..652e287df9d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/SessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/SessionManager.java @@ -764,11 +764,14 @@ public class SessionManager extends ContainerLifeCycle implements org.eclipse.je Session session = _sessionStore.get(key); if (session != null) { - //TODO consider not allowing load of expired sessions inside stores - //if the session we loaded has expired + //If the session we got back has expired if (session.isExpiredAt(System.currentTimeMillis())) { - //Remove the expired session from cache and backing persistent store + //Tell the id manager that this session id should not be used in case other threads + //try to use the same session id in other contexts + _sessionIdManager.removeId(id); + + //Remove the expired session from cache and any backing persistent store try { _sessionStore.delete(key); @@ -778,10 +781,6 @@ public class SessionManager extends ContainerLifeCycle implements org.eclipse.je LOG.warn("Unable to delete expired session {}", key); } - //Tell the id manager that this session id should not be used in case other threads - //try to use the same session id in other contexts - _sessionIdManager.removeId(id); - return null; } @@ -799,9 +798,9 @@ public class SessionManager extends ContainerLifeCycle implements org.eclipse.je _sessionIdManager.removeId(id); return null; } - catch (Exception e1) + catch (Exception other) { - LOG.warn(e1); + LOG.warn(other); return null; } } @@ -992,26 +991,50 @@ public class SessionManager extends ContainerLifeCycle implements org.eclipse.je { _checkingRemoteSessionIdEncoding=remote; } - - + + /* ------------------------------------------------------------ */ /** - * Tell the HttpSessionIdListeners the id changed. - * NOTE: this method must be called LAST in subclass overrides, after the session has been updated - * with the new id. - * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + * Change the session id and tell the HttpSessionIdListeners the id changed. + * */ @Override - public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId) + public void renewSessionId(String oldId, String oldExtendedId, String newId, String newExtendedId) { - if (!_sessionIdListeners.isEmpty()) + try { - Session session = getSession(newClusterId); - HttpSessionEvent event = new HttpSessionEvent(session); - for (HttpSessionIdListener l:_sessionIdListeners) + SessionKey oldKey = SessionKey.getKey(oldId, _context); + SessionKey newKey = SessionKey.getKey(newId, _context); + + Session session = _sessionStore.get(oldKey); + if (session == null) { - l.sessionIdChanged(event, oldClusterId); + LOG.warn("Unable to renew id to "+newId+" for non-existant session "+oldId); + return; } + + //save session with new id + session.getSessionData().setId(newId); + session.setExtendedId(newExtendedId); + session.getSessionData().setLastSaved(0); //forces an insert + _sessionStore.put(newKey, session); + + //remove session with old id + _sessionStore.delete(oldKey); + + //inform the listeners + if (!_sessionIdListeners.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + for (HttpSessionIdListener l:_sessionIdListeners) + { + l.sessionIdChanged(event, oldId); + } + } + } + catch (Exception e) + { + LOG.warn(e); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/StalePeriodStrategy.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/StalePeriodStrategy.java new file mode 100644 index 00000000000..874b0da681f --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/StalePeriodStrategy.java @@ -0,0 +1,70 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session.x; + +/** + * StalePeriodStrategy + * + * + */ +public class StalePeriodStrategy implements StalenessStrategy +{ + protected long _staleMs = 0; + + /** + * @see org.eclipse.jetty.server.session.x.StalenessStrategy#isStale(org.eclipse.jetty.server.session.x.Session) + */ + @Override + public boolean isStale (Session session) + { + if (session == null) + return false; + + //never persisted, must be fresh session + if (session.getSessionData().getLastSaved() == 0) + return false; + + if (_staleMs <= 0) + { + //TODO always stale, never stale?? + return false; + } + else + { + return (session.getSessionData().getAccessed() - session.getSessionData().getLastSaved() >= _staleMs); + } + + } + + + public long getStaleSec () + { + return (_staleMs<=0?0L:_staleMs/1000L); + } + + public void setStaleSec (long sec) + { + if (sec == 0) + _staleMs = 0L; + else + _staleMs = sec * 1000L; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/StalenessStrategy.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/StalenessStrategy.java new file mode 100644 index 00000000000..ac19c85267f --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/x/StalenessStrategy.java @@ -0,0 +1,30 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session.x; + +/** + * StalenessStrategy + * + * + */ +public interface StalenessStrategy +{ + boolean isStale (Session session); +}