Add support for SessionChangedEvents.

Resolves gh-39.
This commit is contained in:
John Blum
2019-06-14 21:47:26 -07:00
parent 54fe8a3c75
commit 8dbf0fd35d
6 changed files with 216 additions and 10 deletions

View File

@@ -42,14 +42,15 @@ import org.apache.geode.cache.query.Index;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport;
import org.springframework.data.gemfire.util.RegionUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.util.StringUtils;
/**
@@ -61,15 +62,16 @@ import org.springframework.util.StringUtils;
* @see java.net.URL
* @see java.time.Instant
* @see org.junit.Test
* @see org.apache.geode.cache.DataPolicy
* @see org.apache.geode.cache.ExpirationAttributes
* @see org.apache.geode.cache.GemFireCache
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.query.Index
* @see org.springframework.context.ApplicationEvent
* @see org.springframework.context.ApplicationContext
* @see org.springframework.context.ApplicationListener
* @see org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport
* @see org.springframework.session.Session
* @see org.springframework.session.SessionRepository
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
* @see org.springframework.session.events.AbstractSessionEvent
* @since 1.1.0
@@ -422,12 +424,12 @@ public abstract class AbstractGemFireIntegrationTests extends ForkingClientServe
* @see org.springframework.context.ApplicationListener
* @see org.springframework.session.events.AbstractSessionEvent
*/
public static class SessionEventListener implements ApplicationListener<AbstractSessionEvent> {
public static class SessionEventListener implements ApplicationListener<ApplicationEvent> {
private volatile AbstractSessionEvent sessionEvent;
private volatile ApplicationEvent sessionEvent;
@SuppressWarnings("unchecked")
public <T extends AbstractSessionEvent> T getSessionEvent() {
public <T extends ApplicationEvent> T getSessionEvent() {
T sessionEvent = (T) this.sessionEvent;
@@ -436,11 +438,12 @@ public abstract class AbstractGemFireIntegrationTests extends ForkingClientServe
return sessionEvent;
}
public void onApplicationEvent(AbstractSessionEvent event) {
@Override
public void onApplicationEvent(@NonNull ApplicationEvent event) {
this.sessionEvent = event;
}
public <T extends AbstractSessionEvent> T waitForSessionEvent(long duration) {
public <T extends ApplicationEvent> T waitForSessionEvent(long duration) {
waitOn(() -> SessionEventListener.this.sessionEvent != null, duration);

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.data.gemfire.config.annotation.web.http;
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,11 +33,13 @@ import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionShortcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
import org.springframework.session.data.gemfire.events.SessionChangedEvent;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.session.events.SessionCreatedEvent;
@@ -125,6 +126,24 @@ public class EnableGemFireHttpSessionEventsIntegrationTests extends AbstractGemF
assertThat(createdSession.isExpired()).isFalse();
}
@Test
public void sessionChangedEvent() {
Session expectedSession = save(createSession());
ApplicationEvent sessionEvent = this.sessionEventListener.getSessionEvent();
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
expectedSession.setAttribute("testKey", "testValue");
save(touch(expectedSession));
sessionEvent = this.sessionEventListener.waitForSessionEvent(500L);
assertThat(sessionEvent).isInstanceOf(SessionChangedEvent.class);
assertThat(((SessionChangedEvent) sessionEvent).<Session>getSession()).isEqualTo(expectedSession);
}
@Test
public void getExistingNonExpiredSession() {
@@ -212,7 +231,7 @@ public class EnableGemFireHttpSessionEventsIntegrationTests extends AbstractGemF
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
assertThat(sessionEvent.<Session>getSession()).isEqualTo(expectedSession);
assertThat(sessionEvent.getSessionId()).isEqualTo(expiredSession.getId());
assertThat(this.gemfireSessionRepository.<Session>findById(sessionEvent.getSessionId())).isNull();
assertThat(this.gemfireSessionRepository.findById(sessionEvent.getSessionId())).isNull();
}
@Test
@@ -220,7 +239,7 @@ public class EnableGemFireHttpSessionEventsIntegrationTests extends AbstractGemF
String expectedSessionId = UUID.randomUUID().toString();
assertThat(this.gemfireSessionRepository.<Session>findById(expectedSessionId)).isNull();
assertThat(this.gemfireSessionRepository.findById(expectedSessionId)).isNull();
this.gemfireSessionRepository.deleteById(expectedSessionId);

View File

@@ -62,6 +62,7 @@ import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import org.springframework.session.data.gemfire.events.SessionChangedEvent;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.session.data.gemfire.support.IsDirtyPredicate;
import org.springframework.session.data.gemfire.support.SessionIdHolder;
@@ -1541,6 +1542,25 @@ public abstract class AbstractGemFireOperationsSessionRepository
.publishEvent(newSessionExpiredEvent(toSession(event.getOldValue(), it.getKey()))));
}
/**
* Callback method triggered when an entry is updated in the {@link Session} cache {@link Region}.
*
* @param event {@link EntryEvent} containing the details of the cache operation.
* @see org.springframework.session.data.gemfire.events.SessionChangedEvent
* @see org.springframework.session.Session
* @see org.apache.geode.cache.EntryEvent
* @see #newSessionChangedEvent(Session)
* @see #publishEvent(ApplicationEvent)
* @see #toSession(Object, Object)
*/
@Override
public void afterUpdate(EntryEvent<Object, Session> event) {
Optional.ofNullable(event)
.ifPresent(it -> getSessionRepository()
.publishEvent(newSessionChangedEvent(toSession(event.getNewValue(), it.getKey()))));
}
/**
* Constructs a new {@link SessionCreatedEvent} initialized with the given {@link Session},
* using the {@link #getSessionRepository() SessionRepository} as the event source.
@@ -1555,6 +1575,20 @@ public abstract class AbstractGemFireOperationsSessionRepository
return new SessionCreatedEvent(getSessionRepository(), session);
}
/**
* Constructs a new {@link SessionChangedEvent} initialized with the given {@link Session},
* using the {@link #getSessionRepository() SessionRepository} as the event source.
*
* @param session {@link Session} that is the subject of the {@link ApplicationEvent change event}.
* @return a new {@link SessionChangedEvent}.
* @see org.springframework.session.data.gemfire.events.SessionChangedEvent
* @see org.springframework.session.Session
* @see #getSessionRepository()
*/
protected SessionChangedEvent newSessionChangedEvent(Session session) {
return new SessionChangedEvent(getSessionRepository(), session);
}
/**
* Constructs a new {@link SessionDeletedEvent} initialized with the given {@link Session},
* using the {@link #getSessionRepository() SessionRepository} as the event source.

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2019 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
*
* https://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.data.gemfire.events;
import org.springframework.context.ApplicationEvent;
import org.springframework.session.Session;
/**
* {@link SessionChangedEvent} is a Spring {@link ApplicationEvent} fire when the {@link Session} state changes.
*
* @author John Blum
* @see org.springframework.context.ApplicationEvent
* @see org.springframework.session.Session
* @since 2.2.0
*/
public class SessionChangedEvent extends ApplicationEvent {
private final Session session;
/**
* Constructs a new instance of {@link SessionChangedEvent} initialized with the given {@link Object source}
* and {@link Session}.
*
* @param source {@link Object} referencing the source of the event.
* @param session {@link Session} that changed.
* @see org.springframework.session.Session
*/
public SessionChangedEvent(Object source, Session session) {
super(source);
this.session = session;
}
/**
* Gets the {@link Session} that was changed.
*
* @param <S> {@link Class type} of {@link Session}.
* @return a reference to the {@link Session} that is the subject of the change event.
* @see org.springframework.session.Session
*/
@SuppressWarnings("unchecked")
public <S extends Session> S getSession() {
return (S) this.session;
}
}

View File

@@ -88,6 +88,7 @@ import org.springframework.lang.Nullable;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import org.springframework.session.data.gemfire.events.SessionChangedEvent;
import org.springframework.session.data.gemfire.support.DeltaAwareDirtyPredicate;
import org.springframework.session.data.gemfire.support.EqualsDirtyPredicate;
import org.springframework.session.data.gemfire.support.GemFireOperationsSessionRepositorySupport;
@@ -1401,6 +1402,41 @@ public class AbstractGemFireOperationsSessionRepositoryTests {
verify(this.sessionRepository, never()).publishEvent(any(ApplicationEvent.class));
}
@Test
@SuppressWarnings("unchecked")
public void afterUpdateHandlesSessionPublishesSessionChangedEvent() {
SessionEventHandlerCacheListenerAdapter sessionEventHandler =
spy(this.sessionRepository.newSessionEventHandler());
EntryEvent mockEntryEvent = mock(EntryEvent.class);
when(mockEntryEvent.getKey()).thenReturn("1");
when(mockEntryEvent.getNewValue()).thenReturn(this.mockSession);
sessionEventHandler.afterUpdate(mockEntryEvent);
verify(mockEntryEvent, times(1)).getKey();
verify(mockEntryEvent, times(1)).getNewValue();
verify(mockEntryEvent, never()).getOldValue();
verify(sessionEventHandler, times(1)).newSessionChangedEvent(eq(this.mockSession));
verify(sessionEventHandler, times(1)).toSession(eq(this.mockSession), eq("1"));
verify(this.sessionRepository, times(1)).publishEvent(isA(SessionChangedEvent.class));
}
@Test
public void afterUpdateHandlesNullEntryEventDoesNotPublishSessionChangedEvent() {
SessionEventHandlerCacheListenerAdapter sessionEventHandler =
spy(this.sessionRepository.newSessionEventHandler());
sessionEventHandler.afterUpdate(null);
verify(sessionEventHandler, never()).newSessionChangedEvent(any(Session.class));
verify(sessionEventHandler, never()).toSession(any(), any());
verify(this.sessionRepository, never()).publishEvent(any(ApplicationEvent.class));
}
@Test
public void sessionCreateCreateExpireRecreatePublishesSessionEventsCreateExpireCreate() {

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2019 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
*
* https://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.data.gemfire.events;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.session.Session;
/**
* Unit Tests for {@link SessionChangedEvent}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.junit.MockitoJUnitRunner
* @see org.springframework.session.Session
* @since 2.2.0
*/
@RunWith(MockitoJUnitRunner.class)
public class SessionChangedEventUnitTests {
@Mock
private Session mockSession;
@Test
public void constructSessionChangedEvent() {
Object source = new Object();
SessionChangedEvent event = new SessionChangedEvent(source, this.mockSession);
assertThat(event).isNotNull();
assertThat(event.getSource()).isEqualTo(source);
assertThat(event.<Session>getSession()).isEqualTo(this.mockSession);
}
}