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.
* </p>
*
* @since 1.0
* @author Rob Winch
*/
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.
*
* @author Rob Winch
* @since 4.0
* @since 1.0
*/
public class MapSessionRepository implements SessionRepository<Session> {
private final Map<String,Session> sessions;

View File

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

View File

@@ -19,7 +19,7 @@ package org.springframework.session;
* A repository interface for managing {@link Session} instances.
*
* @author Rob Winch
* @since 4.0
* @since 1.0
*/
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.Session;
import org.springframework.session.SessionRepository;
import org.springframework.util.Assert;
import java.util.HashMap;
import java.util.Map;
@@ -27,35 +28,120 @@ import java.util.Set;
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
* {@link org.springframework.session.web.SessionRepositoryFilter}.
* <p>
* 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
*
* @author Rob Winch
*/
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:";
/**
* The key in the Hash representing {@link org.springframework.session.Session#getCreationTime()}
*/
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";
/**
* The key in the Hash representing {@link org.springframework.session.Session#getLastAccessedTime()}
*/
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 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;
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
* 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) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
@@ -68,7 +154,7 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
@Override
public RedisSession getSession(String id) {
Map<Object, Object> entries = getOperations(id).entries();
Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
if(entries.isEmpty()) {
return null;
}
@@ -92,7 +178,7 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
@Override
public void delete(String sessionId) {
String key = getKey(sessionId);
this.redisTemplate.delete(key);
this.redisOperations.delete(key);
}
@Override
@@ -104,19 +190,42 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
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) {
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);
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 {
private final MapSession cached;
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() {
this(new MapSession());
delta.put(CREATION_TIME_ATTR, getCreationTime());
@@ -124,7 +233,13 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
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) {
Assert.notNull("MapSession cannot be null");
this.cached = cached;
}
@@ -182,10 +297,13 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
delta.put(SESSION_ATTR_PREFIX + attributeName, null);
}
/**
* Saves any attributes that have been changed and updates the expiration of this session.
*/
private void saveDelta() {
getOperations(getId()).putAll(delta);
getOperations(getId()).expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
getSessionBoundHashOperations(getId()).putAll(delta);
getSessionBoundHashOperations(getId()).expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
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".
*
* 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
* {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will be marked as secure. For example:
* session id. The cookie will be marked as a session cookie, use the context path for the path of the cookie, marked as
* HTTPOnly, and if {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will be marked as
* secure. For example:
*
* <pre>
* 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>
*
* 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
* </pre>
*
* @since 1.0
* @author Rob Winch
*/
public final class CookieHttpSessionStrategy implements HttpSessionStrategy {

View File

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