Add SessionExpiredEvent and SessionDeletedEvent

Fixes gh-258
This commit is contained in:
Mark Anderson
2015-08-09 22:46:25 +01:00
committed by Rob Winch
parent 249361bb7f
commit 7518306975
11 changed files with 156 additions and 49 deletions

View File

@@ -33,7 +33,7 @@ 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.SessionDestroyedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@@ -46,7 +46,7 @@ public class RedisOperationsSessionRepositoryITests<S extends Session> {
private SessionRepository<S> repository;
@Autowired
private SessionDestroyedEventRegistry registry;
private SessionDeletedEventRegistry registry;
private final Object lock = new Object();
@@ -100,11 +100,11 @@ public class RedisOperationsSessionRepositoryITests<S extends Session> {
assertThat(session.getAttribute("1")).isEqualTo("2");
}
static class SessionDestroyedEventRegistry implements ApplicationListener<SessionDestroyedEvent> {
static class SessionDeletedEventRegistry implements ApplicationListener<SessionDeletedEvent> {
private boolean receivedEvent;
private Object lock;
public void onApplicationEvent(SessionDestroyedEvent event) {
public void onApplicationEvent(SessionDeletedEvent event) {
receivedEvent = true;
synchronized (lock) {
lock.notifyAll();
@@ -131,8 +131,8 @@ public class RedisOperationsSessionRepositoryITests<S extends Session> {
}
@Bean
public SessionDestroyedEventRegistry sessionDestroyedEventRegistry() {
return new SessionDestroyedEventRegistry();
public SessionDeletedEventRegistry sessionDestroyedEventRegistry() {
return new SessionDeletedEventRegistry();
}
}
}

View File

@@ -33,7 +33,7 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.ExpiringSession;
import org.springframework.session.SessionRepository;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@@ -46,7 +46,7 @@ public class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Expirin
private SessionRepository<S> repository;
@Autowired
private SessionDestroyedEventRegistry registry;
private SessionExpiredEventRegistry registry;
private final Object lock = new Object();
@@ -56,7 +56,7 @@ public class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Expirin
}
@Test
public void expireFiresSessionDestroyedEvent() throws InterruptedException {
public void expireFiresSessionExpiredEvent() throws InterruptedException {
S toSave = repository.createSession();
toSave.setAttribute("a", "b");
Authentication toSaveToken = new UsernamePasswordAuthenticationToken("user","password", AuthorityUtils.createAuthorityList("ROLE_USER"));
@@ -83,11 +83,11 @@ public class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Expirin
assertThat(registry.receivedEvent()).isTrue();
}
static class SessionDestroyedEventRegistry implements ApplicationListener<SessionDestroyedEvent> {
static class SessionExpiredEventRegistry implements ApplicationListener<SessionExpiredEvent> {
private boolean receivedEvent;
private Object lock;
public void onApplicationEvent(SessionDestroyedEvent event) {
public void onApplicationEvent(SessionExpiredEvent event) {
synchronized (lock) {
receivedEvent = true;
lock.notifyAll();
@@ -114,8 +114,8 @@ public class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Expirin
}
@Bean
public SessionDestroyedEventRegistry sessionDestroyedEventRegistry() {
return new SessionDestroyedEventRegistry();
public SessionExpiredEventRegistry sessionDestroyedEventRegistry() {
return new SessionExpiredEventRegistry();
}
}
}

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.session;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -26,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
* distributed maps provided by NoSQL stores like Redis and Hazelcast.
*
* <p>
* The implementation does NOT support firing {@link SessionDestroyedEvent}.
* The implementation does NOT support firing {@link SessionDeletedEvent} or {@link SessionExpiredEvent}.
* </p>
*
* @author Rob Winch

View File

@@ -30,7 +30,8 @@ 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.SessionDestroyedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.Assert;
@@ -41,7 +42,7 @@ import org.springframework.util.Assert;
* {@link org.springframework.data.redis.core.RedisOperations}. In a web
* environment, this is typically used in combination with
* {@link SessionRepositoryFilter}. This implementation supports
* {@link SessionDestroyedEvent} through {@link SessionMessageListener}.
* {@link SessionDeletedEvent} and {@link SessionExpiredEvent} through {@link SessionMessageListener}.
* </p>
*
* <h2>Creating a new instance</h2>

View File

@@ -21,12 +21,14 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.util.Assert;
/**
* Listen for Redis {@link Message} notifications. If it is a "del" or "expired"
* translate into a {@link SessionDestroyedEvent}.
* Listen for Redis {@link Message} notifications. If it is a "del"
* translate into a {@link SessionDeletedEvent}. If it is an "expired"
* translate into a {@link SessionExpiredEvent}.
*
* @author Rob Winch
* @since 1.0
@@ -69,7 +71,11 @@ public class SessionMessageListener implements MessageListener {
logger.debug("Publishing SessionDestroyedEvent for session " + sessionId);
}
publishEvent(new SessionDestroyedEvent(this, sessionId));
if(channel.endsWith(":del")) {
publishEvent(new SessionDeletedEvent(this, sessionId));
} else {
publishEvent(new SessionExpiredEvent(this, sessionId));
}
}
private void publishEvent(ApplicationEvent event) {

View File

@@ -0,0 +1,36 @@
/*
* 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 via deletion.
*
* @author Rob Winch
* @since 1.1
*
*/
@SuppressWarnings("serial")
public class SessionDeletedEvent extends SessionDestroyedEvent {
public SessionDeletedEvent(Object source, String sessionId) {
super(source, sessionId);
}
}

View File

@@ -17,19 +17,16 @@ 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 destroyed either explicitly or via
* expiration.
* Base class for events fired when a {@link Session} is destroyed explicitly.
*
* @author Rob Winch
* @since 1.0
*
*/
@SuppressWarnings("serial")
public class SessionDestroyedEvent extends ApplicationEvent {
public abstract class SessionDestroyedEvent extends ApplicationEvent {
private final String sessionId;
public SessionDestroyedEvent(Object source, String sessionId) {

View File

@@ -0,0 +1,35 @@
/*
* 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 via expiration.
*
* @author Rob Winch
* @since 1.1
*
*/
@SuppressWarnings("serial")
public class SessionExpiredEvent extends SessionDestroyedEvent {
public SessionExpiredEvent(Object source, String sessionId) {
super(source, sessionId);
}
}

View File

@@ -31,6 +31,7 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.connection.Message;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.events.SessionExpiredEvent;
/**
@@ -47,7 +48,10 @@ public class SessionMessageListenerTests {
Message message;
@Captor
ArgumentCaptor<SessionDestroyedEvent> event;
ArgumentCaptor<SessionDestroyedEvent> deletedEvent;
@Captor
ArgumentCaptor<SessionExpiredEvent> expiredEvent;
byte[] pattern;
@@ -76,18 +80,28 @@ public class SessionMessageListenerTests {
listener.onMessage(message, pattern);
verify(eventPublisher).publishEvent(event.capture());
assertThat(event.getValue().getSessionId()).isEqualTo("123");
verify(eventPublisher).publishEvent(deletedEvent.capture());
assertThat(deletedEvent.getValue().getSessionId()).isEqualTo("123");
}
@Test
public void onMessageSource() throws Exception {
public void onMessageDelSource() throws Exception {
mockMessage("__keyevent@0__:del","spring:session:sessions:123");
listener.onMessage(message, pattern);
verify(eventPublisher).publishEvent(event.capture());
assertThat(event.getValue().getSource()).isEqualTo(listener);
verify(eventPublisher).publishEvent(deletedEvent.capture());
assertThat(deletedEvent.getValue().getSource()).isEqualTo(listener);
}
@Test
public void onMessageExpiredSource() throws Exception {
mockMessage("__keyevent@0__:expired","spring:session:sessions:123");
listener.onMessage(message, pattern);
verify(eventPublisher).publishEvent(expiredEvent.capture());
assertThat(expiredEvent.getValue().getSource()).isEqualTo(listener);
}
@Test
@@ -96,8 +110,8 @@ public class SessionMessageListenerTests {
listener.onMessage(message, pattern);
verify(eventPublisher).publishEvent(event.capture());
assertThat(event.getValue().getSessionId()).isEqualTo("543");
verify(eventPublisher).publishEvent(expiredEvent.capture());
assertThat(expiredEvent.getValue().getSessionId()).isEqualTo("543");
}
@Test

View File

@@ -33,7 +33,8 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.session.web.socket.events.SessionConnectEvent;
import org.springframework.session.web.socket.server.SessionRepositoryMessageInterceptor;
import org.springframework.test.util.ReflectionTestUtils;
@@ -58,7 +59,9 @@ public class WebSocketRegistryListenerTests {
SessionDisconnectEvent disconnect;
SessionDestroyedEvent destroyed;
SessionDeletedEvent deleted;
SessionExpiredEvent expired;
Map<String, Object> attributes;
@@ -89,24 +92,35 @@ public class WebSocketRegistryListenerTests {
connect = new SessionConnectEvent(listener,wsSession);
connect2 = new SessionConnectEvent(listener,wsSession2);
disconnect = new SessionDisconnectEvent(listener, message, wsSession.getId(), CloseStatus.NORMAL);
destroyed = new SessionDestroyedEvent(listener, sessionId);
deleted = new SessionDeletedEvent(listener, sessionId);
expired = new SessionExpiredEvent(listener, sessionId);
}
@Test
public void onApplicationEventConnectSessionDestroyed() throws Exception {
public void onApplicationEventConnectSessionDeleted() throws Exception {
listener.onApplicationEvent(connect);
listener.onApplicationEvent(destroyed);
listener.onApplicationEvent(deleted);
verify(wsSession).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
}
@Test
public void onApplicationEventConnectSessionExpired() throws Exception {
listener.onApplicationEvent(connect);
listener.onApplicationEvent(expired);
verify(wsSession).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
}
@Test
public void onApplicationEventConnectSessionDestroyedNullPrincipal() throws Exception {
public void onApplicationEventConnectSessionDeletedNullPrincipal() throws Exception {
when(wsSession.getPrincipal()).thenReturn(null);
listener.onApplicationEvent(connect);
listener.onApplicationEvent(destroyed);
listener.onApplicationEvent(deleted);
verify(wsSession,times(0)).close(any(CloseStatus.class));
}
@@ -116,7 +130,7 @@ public class WebSocketRegistryListenerTests {
listener.onApplicationEvent(connect);
listener.onApplicationEvent(disconnect);
listener.onApplicationEvent(destroyed);
listener.onApplicationEvent(deleted);
verify(wsSession,times(0)).close(any(CloseStatus.class));
}
@@ -150,7 +164,7 @@ public class WebSocketRegistryListenerTests {
listener.onApplicationEvent(connect2);
listener.onApplicationEvent(disconnect);
listener.onApplicationEvent(destroyed);
listener.onApplicationEvent(deleted);
verify(wsSession2).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
verify(wsSession,times(0)).close(any(CloseStatus.class));