Add SessionCreatedEvent
Fixes gh-261
This commit is contained in:
@@ -284,7 +284,7 @@ Instead, developers should prefer interacting with `SessionRepository` and `Sess
|
||||
|
||||
`RedisOperationsSessionRepository` is a `SessionRepository` that is implemented using Spring Data's `RedisOperations`.
|
||||
In a web environment, this is typically used in combination with `SessionRepositoryFilter`.
|
||||
The implementation supports `SessionDeletedEvent` and `SessionExpiredEvent` through `SessionMessageListener`.
|
||||
The implementation supports `SessionDestroyedEvent` and `SessionCreatedEvent` through `SessionMessageListener`.
|
||||
|
||||
[[api-redisoperationssessionrepository-new]]
|
||||
==== Instantiating a RedisOperationsSessionRepository
|
||||
@@ -453,6 +453,14 @@ XML Configuraiton can use the following:
|
||||
include::{docs-test-resources-dir}docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml[tags=configure-redis-action]
|
||||
----
|
||||
|
||||
[[api-redisoperationssessionrepository-sessioncreatedevent]]
|
||||
==== SessionCreatedEvent
|
||||
|
||||
When a session is created an event is sent to Redis with the channel of `spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe`
|
||||
such that `33fdd1b6-b496-4b33-9f7d-df96679d32fe` is the session id. The body of the event will be the session that was created.
|
||||
|
||||
If registered as a MessageListener (default), then `RedisOperationsSessionRepository` will then translate the Redis message into a `SessionCreatedEvent`.
|
||||
|
||||
[[api-redisoperationssessionrepository-cli]]
|
||||
==== Viewing the Session in Redis
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -46,14 +48,7 @@ public class RedisOperationsSessionRepositoryITests<S extends Session> {
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@Autowired
|
||||
private SessionDestroyedEventRegistry registry;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
registry.setLock(lock);
|
||||
}
|
||||
private SessionEventRegistry registry;
|
||||
|
||||
@Test
|
||||
public void saves() throws InterruptedException {
|
||||
@@ -65,22 +60,25 @@ public class RedisOperationsSessionRepositoryITests<S extends Session> {
|
||||
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
|
||||
toSaveContext.setAuthentication(toSaveToken);
|
||||
toSave.setAttribute("SPRING_SECURITY_CONTEXT", toSaveContext);
|
||||
registry.clear();
|
||||
|
||||
repository.save(toSave);
|
||||
|
||||
assertThat(registry.receivedEvent()).isTrue();
|
||||
assertThat(registry.getEvent()).isInstanceOf(SessionCreatedEvent.class);
|
||||
|
||||
Session session = repository.getSession(toSave.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(toSave.getId());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(session.getAttributeNames());
|
||||
assertThat(session.getAttribute(expectedAttributeName)).isEqualTo(toSave.getAttribute(expectedAttributeName));
|
||||
|
||||
registry.clear();
|
||||
|
||||
repository.delete(toSave.getId());
|
||||
|
||||
assertThat(repository.getSession(toSave.getId())).isNull();
|
||||
synchronized (lock) {
|
||||
lock.wait(3000);
|
||||
}
|
||||
assertThat(registry.receivedEvent()).isTrue();
|
||||
assertThat(registry.getEvent()).isInstanceOf(SessionDestroyedEvent.class);
|
||||
|
||||
|
||||
assertThat(registry.getEvent().getSession().getAttribute(expectedAttributeName)).isEqualTo(expectedAttributeValue);
|
||||
@@ -105,27 +103,38 @@ public class RedisOperationsSessionRepositoryITests<S extends Session> {
|
||||
assertThat(session.getAttribute("1")).isEqualTo("2");
|
||||
}
|
||||
|
||||
static class SessionDestroyedEventRegistry implements ApplicationListener<SessionDestroyedEvent> {
|
||||
private SessionDestroyedEvent event;
|
||||
private Object lock;
|
||||
static class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||
private AbstractSessionEvent event;
|
||||
private final Object lock = new Object();
|
||||
|
||||
public void onApplicationEvent(SessionDestroyedEvent event) {
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
this.event = event;
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean receivedEvent() {
|
||||
return this.event != null;
|
||||
public void clear() {
|
||||
this.event = null;
|
||||
}
|
||||
|
||||
public SessionDestroyedEvent getEvent() {
|
||||
return event;
|
||||
public boolean receivedEvent() throws InterruptedException {
|
||||
return waitForEvent() != null;
|
||||
}
|
||||
|
||||
public void setLock(Object lock) {
|
||||
this.lock = lock;
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends AbstractSessionEvent> E getEvent() throws InterruptedException {
|
||||
return (E) waitForEvent();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends AbstractSessionEvent> E waitForEvent() throws InterruptedException {
|
||||
synchronized(lock) {
|
||||
if(event == null) {
|
||||
lock.wait(3000);
|
||||
}
|
||||
}
|
||||
return (E) event;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +149,8 @@ public class RedisOperationsSessionRepositoryITests<S extends Session> {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionDestroyedEventRegistry sessionDestroyedEventRegistry() {
|
||||
return new SessionDestroyedEventRegistry();
|
||||
public SessionEventRegistry sessionEventRegistry() {
|
||||
return new SessionEventRegistry();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,13 +30,17 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.BoundHashOperations;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -126,6 +130,21 @@ import org.springframework.util.Assert;
|
||||
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
|
||||
* </pre>
|
||||
*
|
||||
* <h3>SessionCreatedEvent</h3>
|
||||
*
|
||||
* <p>
|
||||
* When a session is created an event is sent to Redis with the channel of
|
||||
* "spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe" such
|
||||
* that "33fdd1b6-b496-4b33-9f7d-df96679d32fe" is the sesion id. The body of the
|
||||
* event will be the session that was created.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If registered as a {@link MessageListener}, then
|
||||
* {@link RedisOperationsSessionRepository} will then translate the Redis
|
||||
* message into a {@link SessionCreatedEvent}.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Expiration</h3>
|
||||
*
|
||||
* <p>
|
||||
@@ -156,11 +175,11 @@ import org.springframework.util.Assert;
|
||||
* <p>
|
||||
* Spring Session relies on the expired and delete
|
||||
* <a href="http://redis.io/topics/notifications">keyspace notifications</a>
|
||||
* from Redis to fire a SessionDestroyedEvent. It is the
|
||||
* SessionDestroyedEvent that ensures resources associated with the Session
|
||||
* are cleaned up. For example, when using Spring Session's WebSocket support
|
||||
* the Redis expired or delete event is what triggers any WebSocket connections
|
||||
* associated with the session to be closed.
|
||||
* from Redis to fire a SessionDestroyedEvent. It is the SessionDestroyedEvent
|
||||
* that ensures resources associated with the Session are cleaned up. For
|
||||
* example, when using Spring Session's WebSocket support the Redis expired or
|
||||
* delete event is what triggers any WebSocket connections associated with the
|
||||
* session to be closed.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
@@ -228,6 +247,11 @@ import org.springframework.util.Assert;
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class RedisOperationsSessionRepository implements SessionRepository<RedisOperationsSessionRepository.RedisSession>, MessageListener {
|
||||
/**
|
||||
* The prefix for SessionCreated event channel. The suffix is the session id.
|
||||
*/
|
||||
private static final String SPRING_SESSION_CREATED_PREFIX = "spring:session:event:created:";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SessionMessageListener.class);
|
||||
|
||||
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
|
||||
@@ -318,6 +342,10 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
|
||||
public void save(RedisSession session) {
|
||||
session.saveDelta();
|
||||
if(session.isNew()) {
|
||||
this.sessionRedisOperations.convertAndSend(SPRING_SESSION_CREATED_PREFIX + session.getId(), session.delta);
|
||||
session.setNew(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(cron="0 * * * * *")
|
||||
@@ -343,6 +371,17 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
if(entries.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
MapSession loaded = loadSession(id, entries);
|
||||
if(!allowExpired && loaded.isExpired()) {
|
||||
return null;
|
||||
}
|
||||
RedisSession result = new RedisSession(loaded);
|
||||
result.originalLastAccessTime = loaded.getLastAccessedTime() + TimeUnit.SECONDS.toMillis(loaded.getMaxInactiveIntervalInSeconds());
|
||||
result.setLastAccessedTime(System.currentTimeMillis());
|
||||
return result;
|
||||
}
|
||||
|
||||
private MapSession loadSession(String id, Map<Object, Object> entries) {
|
||||
MapSession loaded = new MapSession();
|
||||
loaded.setId(id);
|
||||
for(Map.Entry<Object,Object> entry : entries.entrySet()) {
|
||||
@@ -357,13 +396,7 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
loaded.setAttribute(key.substring(SESSION_ATTR_PREFIX.length()), entry.getValue());
|
||||
}
|
||||
}
|
||||
if(!allowExpired && loaded.isExpired()) {
|
||||
return null;
|
||||
}
|
||||
RedisSession result = new RedisSession(loaded);
|
||||
result.originalLastAccessTime = loaded.getLastAccessedTime() + TimeUnit.SECONDS.toMillis(loaded.getMaxInactiveIntervalInSeconds());
|
||||
result.setLastAccessedTime(System.currentTimeMillis());
|
||||
return result;
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public void delete(String sessionId) {
|
||||
@@ -392,8 +425,6 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
}
|
||||
return redisSession;
|
||||
}
|
||||
|
||||
|
||||
public void onMessage(Message message, byte[] pattern) {
|
||||
byte[] messageChannel = message.getChannel();
|
||||
byte[] messageBody = message.getBody();
|
||||
@@ -403,6 +434,14 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
|
||||
String channel = new String(messageChannel);
|
||||
|
||||
|
||||
if(channel.startsWith(SPRING_SESSION_CREATED_PREFIX)) {
|
||||
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
|
||||
Map<Object,Object> loaded = (Map<Object, Object>) serializer.deserialize(message.getBody());
|
||||
handleCreated(loaded, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
String body = new String(messageBody);
|
||||
if(!body.startsWith("spring:session:sessions:expires")) {
|
||||
return;
|
||||
@@ -430,6 +469,12 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
}
|
||||
}
|
||||
|
||||
public void handleCreated(Map<Object,Object> loaded, String channel) {
|
||||
String id = channel.substring(channel.lastIndexOf(":"));
|
||||
ExpiringSession session = loadSession(id, loaded);
|
||||
publishEvent(new SessionCreatedEvent(this, session));
|
||||
}
|
||||
|
||||
private void handleDeleted(String sessionId, RedisSession session) {
|
||||
if(session == null) {
|
||||
publishEvent(new SessionDeletedEvent(this, sessionId));
|
||||
@@ -508,6 +553,7 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
private final MapSession cached;
|
||||
private Long originalLastAccessTime;
|
||||
private Map<String, Object> delta = new HashMap<String,Object>();
|
||||
private boolean isNew;
|
||||
|
||||
/**
|
||||
* Creates a new instance ensuring to mark all of the new attributes to be persisted in the next save operation.
|
||||
@@ -517,6 +563,7 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
delta.put(CREATION_TIME_ATTR, getCreationTime());
|
||||
delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds());
|
||||
delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime());
|
||||
this.isNew = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -531,6 +578,10 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
this.cached = cached;
|
||||
}
|
||||
|
||||
public void setNew(boolean isNew) {
|
||||
this.isNew = isNew;
|
||||
}
|
||||
|
||||
public void setLastAccessedTime(long lastAccessedTime) {
|
||||
cached.setLastAccessedTime(lastAccessedTime);
|
||||
delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime());
|
||||
@@ -540,6 +591,10 @@ public class RedisOperationsSessionRepository implements SessionRepository<Redis
|
||||
return cached.isExpired();
|
||||
}
|
||||
|
||||
public boolean isNew() {
|
||||
return isNew;
|
||||
}
|
||||
|
||||
public long getCreationTime() {
|
||||
return cached.getCreationTime();
|
||||
}
|
||||
|
||||
@@ -31,18 +31,19 @@ import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.redis.connection.MessageListener;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
|
||||
import org.springframework.session.data.redis.SessionMessageListener;
|
||||
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
|
||||
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||
import org.springframework.session.web.http.HttpSessionStrategy;
|
||||
@@ -73,11 +74,13 @@ public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoad
|
||||
|
||||
@Bean
|
||||
public RedisMessageListenerContainer redisMessageListenerContainer(
|
||||
RedisConnectionFactory connectionFactory, RedisOperationsSessionRepository redisSessionMessageListener) {
|
||||
RedisConnectionFactory connectionFactory, RedisOperationsSessionRepository messageListener) {
|
||||
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
container.addMessageListener(redisSessionMessageListener,
|
||||
Arrays.asList(new PatternTopic("__keyevent@*:del"),new PatternTopic("__keyevent@*:expired")));
|
||||
container.addMessageListener(messageListener,
|
||||
Arrays.asList(new PatternTopic("__keyevent@*:del"), new PatternTopic("__keyevent@*:expired")));
|
||||
container.addMessageListener(messageListener, Arrays.asList(new PatternTopic("spring:session:event:created:*")));
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.session.events;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* For {@link SessionRepository} implementations that support it, this event is
|
||||
* fired when a {@link Session} is updated.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class AbstractSessionEvent extends ApplicationEvent {
|
||||
private final String sessionId;
|
||||
|
||||
private final Session session;
|
||||
|
||||
protected AbstractSessionEvent(Object source, String sessionId) {
|
||||
super(source);
|
||||
this.sessionId = sessionId;
|
||||
this.session = null;
|
||||
}
|
||||
|
||||
AbstractSessionEvent(Object source, Session session) {
|
||||
super(source);
|
||||
this.session = session;
|
||||
this.sessionId = session.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Session} that was destroyed. For some
|
||||
* {@link SessionRepository} implementations it may not be possible to get
|
||||
* the original session in which case this may be null.
|
||||
*
|
||||
* @return the expired {@link Session} or null if the data store does not support obtaining it
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends Session> S getSession() {
|
||||
return (S) session;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.session.events;
|
||||
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* For {@link SessionRepository} implementations that support it, this event is
|
||||
* fired when a {@link Session} is destroyed either explicitly or via
|
||||
* expiration.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SessionCreatedEvent extends AbstractSessionEvent {
|
||||
|
||||
public SessionCreatedEvent(Object source, String sessionId) {
|
||||
super(source, sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source
|
||||
* @param session
|
||||
*/
|
||||
public SessionCreatedEvent(Object source, Session session) {
|
||||
super(source, session);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.session.events;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.session.Session;
|
||||
|
||||
/**
|
||||
@@ -26,36 +25,17 @@ import org.springframework.session.Session;
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SessionDestroyedEvent extends ApplicationEvent {
|
||||
private final String sessionId;
|
||||
|
||||
private final Session session;
|
||||
public class SessionDestroyedEvent extends AbstractSessionEvent {
|
||||
|
||||
public SessionDestroyedEvent(Object source, String sessionId) {
|
||||
super(source);
|
||||
this.sessionId = sessionId;
|
||||
this.session = null;
|
||||
}
|
||||
|
||||
public SessionDestroyedEvent(Object source, Session session) {
|
||||
super(source);
|
||||
this.session = session;
|
||||
this.sessionId = session.getId();
|
||||
super(source, sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Session} that was destroyed. For some
|
||||
* {@link SessionRepository} implementations it may not be possible to get
|
||||
* the original session in which case this may be null.
|
||||
*
|
||||
* @return the expired {@link Session} or null if the data store does not support obtaining it
|
||||
* @param source
|
||||
* @param session
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends Session> S getSession() {
|
||||
return (S) session;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
public SessionDestroyedEvent(Object source, Session session) {
|
||||
super(source, session);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user