This commit is contained in:
Rob Winch
2014-07-03 12:07:52 -05:00
parent 01cf491ed4
commit bad70bb0ca
11 changed files with 390 additions and 245 deletions

View File

@@ -37,6 +37,7 @@ import java.util.UUID;
* This implementation has no synchronization, so it is best to use the copy constructor when working on multiple threads. * This implementation has no synchronization, so it is best to use the copy constructor when working on multiple threads.
* </p> * </p>
* *
* @since 1.0
* @author Rob Winch * @author Rob Winch
*/ */
public final class MapSession implements Session { public final class MapSession implements Session {

View File

@@ -26,7 +26,7 @@ import java.util.concurrent.ConcurrentHashMap;
* distributed maps provided by NoSQL stores like Redis and Hazelcast. * distributed maps provided by NoSQL stores like Redis and Hazelcast.
* *
* @author Rob Winch * @author Rob Winch
* @since 4.0 * @since 1.0
*/ */
public class MapSessionRepository implements SessionRepository<Session> { public class MapSessionRepository implements SessionRepository<Session> {
private final Map<String,Session> sessions; private final Map<String,Session> sessions;

View File

@@ -23,7 +23,7 @@ import java.util.Set;
* Session, or even non web related sessions. * Session, or even non web related sessions.
* *
* @author Rob Winch * @author Rob Winch
* @since 4.0 * @since 1.0
*/ */
public interface Session extends Serializable { public interface Session extends Serializable {
/** /**

View File

@@ -19,7 +19,7 @@ package org.springframework.session;
* A repository interface for managing {@link Session} instances. * A repository interface for managing {@link Session} instances.
* *
* @author Rob Winch * @author Rob Winch
* @since 4.0 * @since 1.0
*/ */
public interface SessionRepository<S extends Session> { public interface SessionRepository<S extends Session> {
/** /**

View File

@@ -20,6 +20,7 @@ import org.springframework.data.redis.core.RedisOperations;
import org.springframework.session.MapSession; import org.springframework.session.MapSession;
import org.springframework.session.Session; import org.springframework.session.Session;
import org.springframework.session.SessionRepository; import org.springframework.session.SessionRepository;
import org.springframework.util.Assert;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -27,35 +28,120 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* A {@link org.springframework.session.SessionRepository} that is implemented using Spring Data's {@link org.springframework.data.redis.core.RedisOperations}. In a web environment, this is typically used in combination with * <p>
* {@link org.springframework.session.web.SessionRepositoryFilter}. * A {@link org.springframework.session.SessionRepository} that is implemented using Spring Data's
* {@link org.springframework.data.redis.core.RedisOperations}. In a web environment, this is typically used in
* combination with {@link org.springframework.session.web.SessionRepositoryFilter}.
* </p>
* *
* <h2>Creating a new instance</h2>
*
* A typical example of how to create a new instance can be seen below:
*
* <pre>
* JedisConnectionFactory factory = new JedisConnectionFactory();
*
* RedisTemplate<String, Session> template = new RedisTemplate<String, Session>();
* template.setKeySerializer(new StringRedisSerializer());
* template.setHashKeySerializer(new StringRedisSerializer());
* template.setConnectionFactory(factory);
*
* RedisOperationsSessionRepository redisSessionRepository = new RedisOperationsSessionRepository(template);
* </pre>
*
* <p>
* For additional information on how to create a RedisTemplate, refer to the
* <a href="http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/">Spring Data Redis Reference</a>.
* </p>
*
* <h2>Storage Details</h2>
*
* <p>
* Each session is stored in Redis as a <a href="http://redis.io/topics/data-types#hashes">Hash</a>. Each session is
* set and updated using the <a href="http://redis.io/commands/hmset">HMSET command</a>. An example of how each session
* is stored can be seen below.
* </p>
*
* <pre>
* HMSET spring-security-sessions:<session-id> creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:<attrName> someAttrValue sessionAttr2:<attrName> someAttrValue2
* </pre>
*
* <p>
* An expiration is associated to each session using the <a href="http://redis.io/commands/expire">EXPIRE command</a> based upon the
* {@link org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession#getMaxInactiveInterval()}.
* For example:
* </p>
*
* <pre>
* EXPIRE spring-security-sessions:<session-id> 1800
* </pre>
*
* <p>
* The {@link RedisSession} keeps track of the properties that have changed and only updates those. This means if an attribute
* is written once and read many times we only need to write that attribute once. For example, assume the session attribute
* "sessionAttr2" from earlier was updated. The following would be executed upon saving:
* </p>
*
* <pre>
* HMSET spring-security-sessions:<session-id> sessionAttr2:<attrName> newValue
* EXPIRE spring-security-sessions:<session-id> 1800
* </pre>
* *
* @since 1.0 * @since 1.0
* *
* @author Rob Winch * @author Rob Winch
*/ */
public class RedisOperationsSessionRepository implements SessionRepository<RedisOperationsSessionRepository.RedisSession> { public class RedisOperationsSessionRepository implements SessionRepository<RedisOperationsSessionRepository.RedisSession> {
/**
* The prefix for each key of the Redis Hash representing a single session. The suffix is the unique session id.
*/
private final String BOUNDED_HASH_KEY_PREFIX = "spring-security-sessions:"; private final String BOUNDED_HASH_KEY_PREFIX = "spring-security-sessions:";
/**
* The key in the Hash representing {@link org.springframework.session.Session#getCreationTime()}
*/
private final String CREATION_TIME_ATTR = "creationTime"; private final String CREATION_TIME_ATTR = "creationTime";
/**
* The key in the Hash representing {@link org.springframework.session.Session#getMaxInactiveInterval()}
*/
private final String MAX_INACTIVE_ATTR = "maxInactiveInterval"; private final String MAX_INACTIVE_ATTR = "maxInactiveInterval";
/**
* The key in the Hash representing {@link org.springframework.session.Session#getLastAccessedTime()}
*/
private final String LAST_ACCESSED_ATTR = "lastAccessedTime"; private final String LAST_ACCESSED_ATTR = "lastAccessedTime";
/**
* The prefix of the key for used for session attributes. The suffix is the name of the session attribute. For
* example, if the session contained an attribute named attributeName, then there would be an entry in the hash named
* sessionAttr:attributeName that mapped to its value.
*/
private final String SESSION_ATTR_PREFIX = "sessionAttr:"; private final String SESSION_ATTR_PREFIX = "sessionAttr:";
private final RedisOperations<String,Session> redisOperations;
private final RedisOperations<String,Session> redisTemplate; /**
* If non-null, this value is used to override {@link RedisSession#setDefaultMaxInactiveInterval(int)}.
*/
private Integer defaultMaxInactiveInterval; private Integer defaultMaxInactiveInterval;
public RedisOperationsSessionRepository(RedisOperations<String, Session> redisTemplate) { /**
this.redisTemplate = redisTemplate; * Creates a new instance. For an example, refer to the class level javadoc.
*
* @param redisOperations The {@link RedisOperations} to use. Cannot be null.
*/
public RedisOperationsSessionRepository(RedisOperations<String, Session> redisOperations) {
Assert.notNull(redisOperations, "RedisOperations cannot be null");
this.redisOperations = redisOperations;
} }
/** /**
* Sets the maximum inactive interval in seconds between requests before newly created sessions will be * Sets the maximum inactive interval in seconds between requests before newly created sessions will be
* invalidated. A negative time indicates that the session will never timeout. * invalidated. A negative time indicates that the session will never timeout. The default is 1800 (30 minutes).
* *
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session} should be kept alive between client requests. * @param defaultMaxInactiveInterval the number of seconds that the {@link Session} should be kept alive between
* client requests.
*/ */
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) { public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
@@ -68,7 +154,7 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
@Override @Override
public RedisSession getSession(String id) { public RedisSession getSession(String id) {
Map<Object, Object> entries = getOperations(id).entries(); Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
if(entries.isEmpty()) { if(entries.isEmpty()) {
return null; return null;
} }
@@ -92,7 +178,7 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
@Override @Override
public void delete(String sessionId) { public void delete(String sessionId) {
String key = getKey(sessionId); String key = getKey(sessionId);
this.redisTemplate.delete(key); this.redisOperations.delete(key);
} }
@Override @Override
@@ -104,19 +190,42 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
return redisSession; return redisSession;
} }
/**
* Gets the Hash key for this session by prefixing it appropriately.
*
* @param sessionId the session id
* @return the Hash key for this session by prefixing it appropriately.
*/
private String getKey(String sessionId) { private String getKey(String sessionId) {
return BOUNDED_HASH_KEY_PREFIX + sessionId; return BOUNDED_HASH_KEY_PREFIX + sessionId;
} }
private BoundHashOperations<String, Object, Object> getOperations(String sessionId) { /**
* Gets the {@link BoundHashOperations} to operate on a {@link Session}
* @param sessionId the id of the {@link Session} to work with
* @return the {@link BoundHashOperations} to operate on a {@link Session}
*/
private BoundHashOperations<String, Object, Object> getSessionBoundHashOperations(String sessionId) {
String key = getKey(sessionId); String key = getKey(sessionId);
return this.redisTemplate.boundHashOps(key); return this.redisOperations.boundHashOps(key);
} }
/**
* A custom implementation of {@link Session} that uses a {@link MapSession} as the basis for its mapping. It keeps
* track of any attributes that have changed. When
* {@link org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession#saveDelta()} is invoked
* all the attributes that have been changed will be persisted.
*
* @since 1.0
* @author Rob Winch
*/
class RedisSession implements Session { class RedisSession implements Session {
private final MapSession cached; private final MapSession cached;
private Map<String, Object> delta = new HashMap<String,Object>(); private Map<String, Object> delta = new HashMap<String,Object>();
/**
* Creates a new instance ensuring to mark all of the new attributes to be persisted in the next save operation.
*/
private RedisSession() { private RedisSession() {
this(new MapSession()); this(new MapSession());
delta.put(CREATION_TIME_ATTR, getCreationTime()); delta.put(CREATION_TIME_ATTR, getCreationTime());
@@ -124,7 +233,13 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime()); delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime());
} }
/**
* Creates a new instance from the provided {@link MapSession}
*
* @param cached the {@MapSession} that represents the persisted session that was retrieved. Cannot be null.
*/
private RedisSession(MapSession cached) { private RedisSession(MapSession cached) {
Assert.notNull("MapSession cannot be null");
this.cached = cached; this.cached = cached;
} }
@@ -182,10 +297,13 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
delta.put(SESSION_ATTR_PREFIX + attributeName, null); delta.put(SESSION_ATTR_PREFIX + attributeName, null);
} }
/**
* Saves any attributes that have been changed and updates the expiration of this session.
*/
private void saveDelta() { private void saveDelta() {
getOperations(getId()).putAll(delta); getSessionBoundHashOperations(getId()).putAll(delta);
getOperations(getId()).expire(getMaxInactiveInterval(), TimeUnit.SECONDS); getSessionBoundHashOperations(getId()).expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
delta.clear(); delta.clear();
} }
} }
} }

View File

@@ -27,12 +27,13 @@ import javax.servlet.http.HttpServletResponse;
* allow specifying a cookie name using {@link CookieHttpSessionStrategy#setCookieName(String)}. The default is "SESSION". * allow specifying a cookie name using {@link CookieHttpSessionStrategy#setCookieName(String)}. The default is "SESSION".
* *
* When a session is created, the HTTP response will have a cookie with the specified cookie name and the value of the * When a session is created, the HTTP response will have a cookie with the specified cookie name and the value of the
* session id. The cookie will be marked as a session cookie, marked as HTTPOnly, and if * session id. The cookie will be marked as a session cookie, use the context path for the path of the cookie, marked as
* {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will be marked as secure. For example: * HTTPOnly, and if {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will be marked as
* secure. For example:
* *
* <pre> * <pre>
* HTTP/1.1 200 OK * HTTP/1.1 200 OK
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Secure; HttpOnly * Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
* </pre> * </pre>
* *
* The client should now include the session in each request by specifying the same cookie in their request. For example: * The client should now include the session in each request by specifying the same cookie in their request. For example:
@@ -50,6 +51,7 @@ import javax.servlet.http.HttpServletResponse;
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly * Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
* </pre> * </pre>
* *
* @since 1.0
* @author Rob Winch * @author Rob Winch
*/ */
public final class CookieHttpSessionStrategy implements HttpSessionStrategy { public final class CookieHttpSessionStrategy implements HttpSessionStrategy {

View File

@@ -47,6 +47,7 @@ import javax.servlet.http.HttpServletResponse;
* x-auth-token: * x-auth-token:
* </pre> * </pre>
* *
* @since 1.0
* @author Rob Winch * @author Rob Winch
*/ */
public class HeaderHttpSessionStrategy implements HttpSessionStrategy { public class HeaderHttpSessionStrategy implements HttpSessionStrategy {

View File

@@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletResponse;
/** /**
* A strategy for mapping HTTP request and responses to a {@link Session}. * A strategy for mapping HTTP request and responses to a {@link Session}.
* *
* @since 1.0
* @author Rob Winch * @author Rob Winch
*/ */
public interface HttpSessionStrategy { public interface HttpSessionStrategy {

View File

@@ -26,6 +26,7 @@ import java.util.Locale;
* Base class for response wrappers which encapsulate the logic for handling an event when the * Base class for response wrappers which encapsulate the logic for handling an event when the
* {@link javax.servlet.http.HttpServletResponse} is committed. * {@link javax.servlet.http.HttpServletResponse} is committed.
* *
* @since 1.0
* @author Rob Winch * @author Rob Winch
*/ */
abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper { abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {

View File

@@ -24,6 +24,7 @@ import java.io.IOException;
* Allows for easily ensuring that a request is only invoked once per request. This is a simplified version of spring-web's * Allows for easily ensuring that a request is only invoked once per request. This is a simplified version of spring-web's
* OncePerRequestFilter and copied to reduce the foot print required to use the session support. * OncePerRequestFilter and copied to reduce the foot print required to use the session support.
* *
* @since 1.0
* @author Rob Winch * @author Rob Winch
*/ */
abstract class OncePerRequestFilter implements Filter { abstract class OncePerRequestFilter implements Filter {

View File

@@ -48,272 +48,292 @@ import java.util.Set;
* <li>The client is notified that the session id is no longer valid with {@link HttpSessionStrategy#onInvalidateSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}</li> * <li>The client is notified that the session id is no longer valid with {@link HttpSessionStrategy#onInvalidateSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}</li>
* </ul> * </ul>
* *
* session id is looked up using the provided {@link HttpSessionStrategy}. The same strategy is used to convey the * @since 1.0
* session id of newly created {@link org.springframework.session.Session}s to the client.
*
* @author Rob Winch * @author Rob Winch
*/ */
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter { public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
private final SessionRepository<S> sessionRepository; private final SessionRepository<S> sessionRepository;
private HttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy(); private HttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();
public SessionRepositoryFilter(SessionRepository<S> sessionRepository) { /**
this.sessionRepository = sessionRepository; * Creates a new instance
} *
* @param sessionRepository the <code>SessionRepository</code> to use. Cannot be null.
*/
public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
Assert.notNull(sessionRepository, "SessionRepository cannot be null");
this.sessionRepository = sessionRepository;
}
/** /**
* Sets the {@link HttpSessionStrategy} to be used. The default is a {@link CookieHttpSessionStrategy}. * Sets the {@link HttpSessionStrategy} to be used. The default is a {@link CookieHttpSessionStrategy}.
* *
* @param httpSessionStrategy the {@link HttpSessionStrategy} to use. Cannot be null. * @param httpSessionStrategy the {@link HttpSessionStrategy} to use. Cannot be null.
*/ */
public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) { public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
Assert.notNull(httpSessionStrategy,"httpSessionIdStrategy cannot be null"); Assert.notNull(httpSessionStrategy,"httpSessionIdStrategy cannot be null");
this.httpSessionStrategy = httpSessionStrategy; this.httpSessionStrategy = httpSessionStrategy;
} }
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response); SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,response); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,response);
try { try {
filterChain.doFilter(wrappedRequest, wrappedResponse); filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally { } finally {
wrappedRequest.commitSession(); wrappedRequest.commitSession();
} }
} }
private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper { /**
* Allows ensuring that the session is saved if the response is committed.
*
* @author Rob Winch
* @since 1.0
*/
private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper {
private final SessionRepositoryRequestWrapper request; private final SessionRepositoryRequestWrapper request;
/** /**
* @param response the response to be wrapped * @param response the response to be wrapped
*/ */
public SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) { public SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) {
super(response); super(response);
this.request = request; Assert.notNull(request, "SessionRepositoryRequestWrapper cannot be null");
} this.request = request;
}
@Override @Override
protected void onResponseCommitted() { protected void onResponseCommitted() {
request.commitSession(); request.commitSession();
} }
} }
/** /**
* A {@link javax.servlet.http.HttpServletRequest} that retrieves the {@link javax.servlet.http.HttpSession} using a * A {@link javax.servlet.http.HttpServletRequest} that retrieves the {@link javax.servlet.http.HttpSession} using a
* {@link org.springframework.session.SessionRepository}. * {@link org.springframework.session.SessionRepository}.
* *
* @author Rob Winch * @author Rob Winch
* @since 4.0 * @since 1.0
*/ */
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
private HttpSessionWrapper currentSession; private HttpSessionWrapper currentSession;
private boolean requestedValidSession; private boolean requestedValidSession;
private final HttpServletResponse response; private final HttpServletResponse response;
private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response) { private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response) {
super(request); super(request);
this.response = response; this.response = response;
} }
private void commitSession() { /**
HttpSessionWrapper wrappedSession = currentSession; * Uses the HttpSessionStrategy to write the session id tot he response and persist the Session.
if(wrappedSession == null) { */
if(isInvalidateClientSession()) { private void commitSession() {
httpSessionStrategy.onInvalidateSession(this, response); HttpSessionWrapper wrappedSession = currentSession;
} if(wrappedSession == null) {
} else { if(isInvalidateClientSession()) {
S session = wrappedSession.session; httpSessionStrategy.onInvalidateSession(this, response);
sessionRepository.save(session); }
httpSessionStrategy.onNewSession(session, this, response); } else {
} S session = wrappedSession.session;
} sessionRepository.save(session);
httpSessionStrategy.onNewSession(session, this, response);
}
}
private boolean isInvalidateClientSession() { private boolean isInvalidateClientSession() {
return currentSession == null && requestedValidSession; return currentSession == null && requestedValidSession;
} }
@Override @Override
public HttpSession getSession(boolean create) { public HttpSession getSession(boolean create) {
if(currentSession != null) { if(currentSession != null) {
return currentSession; return currentSession;
} }
String requestedSessionId = getRequestedSessionId(); String requestedSessionId = getRequestedSessionId();
if(requestedSessionId != null) { if(requestedSessionId != null) {
S session = sessionRepository.getSession(requestedSessionId); S session = sessionRepository.getSession(requestedSessionId);
if(session != null) { if(session != null) {
this.requestedValidSession = true; this.requestedValidSession = true;
session.setLastAccessedTime(System.currentTimeMillis()); session.setLastAccessedTime(System.currentTimeMillis());
currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession = new HttpSessionWrapper(session, getServletContext());
currentSession.setNew(false); currentSession.setNew(false);
return currentSession; return currentSession;
} }
} }
if(!create) { if(!create) {
return null; return null;
} }
S session = sessionRepository.createSession(); S session = sessionRepository.createSession();
currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession = new HttpSessionWrapper(session, getServletContext());
return currentSession; return currentSession;
} }
@Override @Override
public HttpSession getSession() { public HttpSession getSession() {
return getSession(true); return getSession(true);
} }
@Override @Override
public String getRequestedSessionId() { public String getRequestedSessionId() {
return httpSessionStrategy.getRequestedSessionId(this); return httpSessionStrategy.getRequestedSessionId(this);
} }
private final class HttpSessionWrapper implements HttpSession { /**
final S session; * Allows creating an HttpSession from a Session instance.
private final ServletContext servletContext; *
private boolean invalidated; * @author Rob Winch
private boolean old; * @since 1.0
*/
private final class HttpSessionWrapper implements HttpSession {
final S session;
private final ServletContext servletContext;
private boolean invalidated;
private boolean old;
public HttpSessionWrapper(S session, ServletContext servletContext) { public HttpSessionWrapper(S session, ServletContext servletContext) {
this.session = session; this.session = session;
this.servletContext = servletContext; this.servletContext = servletContext;
} }
void updateLastAccessedTime() { void updateLastAccessedTime() {
checkState(); checkState();
session.setLastAccessedTime(System.currentTimeMillis()); session.setLastAccessedTime(System.currentTimeMillis());
} }
@Override @Override
public long getCreationTime() { public long getCreationTime() {
checkState(); checkState();
return session.getCreationTime(); return session.getCreationTime();
} }
@Override @Override
public String getId() { public String getId() {
return session.getId(); return session.getId();
} }
@Override @Override
public long getLastAccessedTime() { public long getLastAccessedTime() {
checkState(); checkState();
return session.getLastAccessedTime(); return session.getLastAccessedTime();
} }
@Override @Override
public ServletContext getServletContext() { public ServletContext getServletContext() {
return servletContext; return servletContext;
} }
@Override @Override
public void setMaxInactiveInterval(int interval) { public void setMaxInactiveInterval(int interval) {
session.setMaxInactiveInterval(interval); session.setMaxInactiveInterval(interval);
} }
@Override @Override
public int getMaxInactiveInterval() { public int getMaxInactiveInterval() {
return session.getMaxInactiveInterval(); return session.getMaxInactiveInterval();
} }
@Override @Override
public HttpSessionContext getSessionContext() { public HttpSessionContext getSessionContext() {
return NOOP_SESSION_CONTEXT; return NOOP_SESSION_CONTEXT;
} }
@Override @Override
public Object getAttribute(String name) { public Object getAttribute(String name) {
checkState(); checkState();
return session.getAttribute(name); return session.getAttribute(name);
} }
@Override @Override
public Object getValue(String name) { public Object getValue(String name) {
return getAttribute(name); return getAttribute(name);
} }
@Override @Override
public Enumeration<String> getAttributeNames() { public Enumeration<String> getAttributeNames() {
checkState(); checkState();
return Collections.enumeration(session.getAttributeNames()); return Collections.enumeration(session.getAttributeNames());
} }
@Override @Override
public String[] getValueNames() { public String[] getValueNames() {
checkState(); checkState();
Set<String> attrs = session.getAttributeNames(); Set<String> attrs = session.getAttributeNames();
return attrs.toArray(new String[0]); return attrs.toArray(new String[0]);
} }
@Override @Override
public void setAttribute(String name, Object value) { public void setAttribute(String name, Object value) {
checkState(); checkState();
session.setAttribute(name, value); session.setAttribute(name, value);
} }
@Override @Override
public void putValue(String name, Object value) { public void putValue(String name, Object value) {
setAttribute(name, value); setAttribute(name, value);
} }
@Override @Override
public void removeAttribute(String name) { public void removeAttribute(String name) {
checkState(); checkState();
session.removeAttribute(name); session.removeAttribute(name);
} }
@Override @Override
public void removeValue(String name) { public void removeValue(String name) {
removeAttribute(name); removeAttribute(name);
} }
@Override @Override
public final void invalidate() { public final void invalidate() {
checkState(); checkState();
this.invalidated = true; this.invalidated = true;
currentSession = null; currentSession = null;
sessionRepository.delete(getId()); sessionRepository.delete(getId());
} }
public void setNew(boolean isNew) { public void setNew(boolean isNew) {
this.old = !isNew; this.old = !isNew;
} }
@Override @Override
public boolean isNew() { public boolean isNew() {
checkState(); checkState();
return !old; return !old;
} }
private void checkState() { private void checkState() {
if(invalidated) { if(invalidated) {
throw new IllegalStateException("The HttpSession has already be invalidated."); throw new IllegalStateException("The HttpSession has already be invalidated.");
} }
} }
} }
} }
private static final HttpSessionContext NOOP_SESSION_CONTEXT = new HttpSessionContext() { private static final HttpSessionContext NOOP_SESSION_CONTEXT = new HttpSessionContext() {
@Override @Override
public HttpSession getSession(String sessionId) { public HttpSession getSession(String sessionId) {
return null; return null;
} }
@Override @Override
public Enumeration<String> getIds() { public Enumeration<String> getIds() {
return EMPTY_ENUMERATION; return EMPTY_ENUMERATION;
} }
}; };
private final static Enumeration<String> EMPTY_ENUMERATION = new Enumeration<String>() { private final static Enumeration<String> EMPTY_ENUMERATION = new Enumeration<String>() {
@Override @Override
public boolean hasMoreElements() { public boolean hasMoreElements() {
return false; return false;
} }
@Override @Override
public String nextElement() { public String nextElement() {
throw new NoSuchElementException("a"); throw new NoSuchElementException("a");
} }
}; };
} }