Re-implement Session ID interest registration using Apache Geode/Pivotal GemFire Region.registerInterest(..) inside a registered, custom CacheListener.

Resolves gh-6.
This commit is contained in:
John Blum
2018-12-12 18:03:11 -08:00
parent c67d4d11b7
commit 019753f86d
6 changed files with 1816 additions and 1247 deletions

View File

@@ -54,10 +54,10 @@ import org.springframework.test.context.web.WebAppConfiguration;
* of the {@link GemFireOperationsSessionRepository} and GemFire's configuration.
*
* @author John Blum
* @since 1.1.0
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.apache.geode.cache.Region
* @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication
* @see org.springframework.session.Session
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
@@ -68,6 +68,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringRunner
* @see org.springframework.test.context.web.WebAppConfiguration
* @since 1.1.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@@ -164,6 +165,7 @@ public class EnableGemFireHttpSessionEventsIntegrationTests extends AbstractGemF
Session expectedSession = save(touch(createSession()));
Session savedSession = this.gemfireSessionRepository.findById(expectedSession.getId());
assertThat(savedSession).isNotNull();
assertThat(savedSession).isEqualTo(expectedSession);
assertThat(savedSession.isExpired()).isFalse();

View File

@@ -38,15 +38,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.geode.DataSerializable;
import org.apache.geode.DataSerializer;
import org.apache.geode.Delta;
import org.apache.geode.Instantiator;
import org.apache.geode.InvalidDeltaException;
import org.apache.geode.cache.AttributesMutator;
import org.apache.geode.cache.EntryEvent;
import org.apache.geode.cache.InterestResultPolicy;
import org.apache.geode.cache.Operation;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.util.CacheListenerAdapter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
@@ -62,6 +60,8 @@ import org.springframework.session.SessionRepository;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.session.data.gemfire.support.SessionIdHolder;
import org.springframework.session.data.gemfire.support.SessionUtils;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
@@ -78,35 +78,45 @@ import org.apache.commons.logging.LogFactory;
* common to all implementations that support {@link SessionRepository} operations backed by Apache Geode.
*
* @author John Blum
* @see java.time.Duration
* @see java.time.Instant
* @see org.apache.geode.DataSerializable
* @see org.apache.geode.DataSerializer
* @see org.apache.geode.Delta
* @see org.apache.geode.Instantiator
* @see org.apache.geode.cache.EntryEvent
* @see org.apache.geode.cache.Operation
* @see org.apache.geode.cache.Region
* @see org.springframework.beans.factory.InitializingBean
* @see org.apache.geode.cache.util.CacheListenerAdapter
* @see org.springframework.context.ApplicationEvent
* @see org.springframework.context.ApplicationEventPublisher
* @see org.springframework.context.ApplicationEventPublisherAware
* @see org.springframework.data.gemfire.GemfireOperations
* @see org.springframework.expression.Expression
* @see org.springframework.session.FindByIndexNameSessionRepository
* @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.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see org.springframework.session.data.gemfire.support.SessionIdHolder
* @see org.springframework.session.events.AbstractSessionEvent
* @see org.springframework.session.events.SessionCreatedEvent
* @see org.springframework.session.events.SessionDeletedEvent
* @see org.springframework.session.events.SessionDestroyedEvent
* @see org.springframework.session.events.SessionExpiredEvent
* @since 1.1.0
*/
public abstract class AbstractGemFireOperationsSessionRepository extends CacheListenerAdapter<Object, Session>
implements ApplicationEventPublisherAware, FindByIndexNameSessionRepository<Session>, InitializingBean {
public abstract class AbstractGemFireOperationsSessionRepository
implements ApplicationEventPublisherAware, FindByIndexNameSessionRepository<Session> {
private static final boolean DEFAULT_REGISTER_INTEREST_ENABLED = false;
private static final boolean DEFAULT_REGISTER_INTEREST_DURABILITY = false;
private static final boolean DEFAULT_REGISTER_INTEREST_RECEIVE_VALUES = false;
private static final AtomicBoolean usingDataSerialization = new AtomicBoolean(false);
private static final InterestResultPolicy DEFAULT_REGISTER_INTEREST_RESULT_POLICY = InterestResultPolicy.NONE;
private boolean registerInterestEnabled = DEFAULT_REGISTER_INTEREST_ENABLED;
private ApplicationEventPublisher applicationEventPublisher = event -> {};
private Duration maxInactiveInterval =
@@ -116,32 +126,111 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
private final Log logger = newLogger();
private final Set<Integer> cachedSessionIds = new ConcurrentSkipListSet<>();
private final Region<Object, Session> sessions;
private String fullyQualifiedRegionName;
private SessionEventHandlerCacheListenerAdapter sessionEventHandler;
private final Set<Integer> interestingSessionIds = new ConcurrentSkipListSet<>();
/**
* Protected, default constructor used by extensions of {@link AbstractGemFireOperationsSessionRepository}
* in order to affect and assess {@link SessionRepository} configuration and state.
*/
protected AbstractGemFireOperationsSessionRepository() {
this.sessions = null;
this.template = null;
}
/**
* Constructs an instance of {@link AbstractGemFireOperationsSessionRepository}
* with a required {@link GemfireOperations} instance used to perform Pivotal GemFire data access operations
* and interactions supporting the SessionRepository operations.
* Constructs a new instance of {@link AbstractGemFireOperationsSessionRepository} initialized with a required
* {@link GemfireOperations} object, which is used to perform Apache Geode or Pivotal GemFire data access operations
* on the cache {@link Region} storing and managing {@link Session} state to support this {@link SessionRepository}
* and its operations.
*
* @param template {@link GemfireOperations} instance used to interact with GemFire; must not be {@literal null}.
* @param template {@link GemfireOperations} object used to interact with the Apache Geode or Pivotal GemFire
* cache {@link Region} storing and managing {@link Session} state; must not be {@literal null}.
* @throws IllegalArgumentException if {@link GemfireOperations} is {@literal null}.
* @see org.springframework.data.gemfire.GemfireOperations
* @see #resolveSessionsRegion(GemfireOperations)
* @see #initializeSessionsRegion(Region)
* @see #newLogger()
*/
public AbstractGemFireOperationsSessionRepository(GemfireOperations template) {
Assert.notNull(template, "GemfireOperations is required");
this.template = template;
this.sessions = initializeSessionsRegion(resolveSessionsRegion(template));
}
/**
* Resolves the cache {@link Region} used to store and manage {@link Session} state
* from the given {@link GemfireOperations} object.
*
* @param gemfireOperations {@link GemfireOperations} object used to resolve the {@link Session} {@link Region}.
* @return the resolve cache {@link Region} used to store and manage {@link Session} state.
* @throws IllegalStateException if the {@link Session Sessions} {@link Region} could not be resolved.
* @see org.springframework.data.gemfire.GemfireOperations
* @see org.springframework.session.Session
* @see org.apache.geode.cache.Region
*/
private Region<Object, Session> resolveSessionsRegion(@Nullable GemfireOperations gemfireOperations) {
return Optional.ofNullable(gemfireOperations)
.filter(GemfireAccessor.class::isInstance)
.map(GemfireAccessor.class::cast)
.<Region<Object, Session>>map(GemfireAccessor::getRegion)
.orElseThrow(() -> newIllegalStateException("The ClusteredSpringSessions Region could not be resolved"));
}
/**
* Initializes the cache {@link Region} used to store and manage {@link Session} state and register this
* {@link SessionRepository} as an Apache Geode / Pivotal GemFire {@link org.apache.geode.cache.CacheListener}.
*
* @param sessionsRegion {@link Region} to initialize.
* @return the given {@link Region}.
* @see org.apache.geode.cache.Region
* @see #newSessionEventHandler()
* @see #newSessionIdInterestRegistrar()
*/
private Region<Object, Session> initializeSessionsRegion(@Nullable Region<Object, Session> sessionsRegion) {
Optional.ofNullable(sessionsRegion)
.map(Region::getAttributesMutator)
.ifPresent(sessionsRegionAttributesMutator -> {
this.sessionEventHandler = newSessionEventHandler();
sessionsRegionAttributesMutator.addCacheListener(this.sessionEventHandler);
if (GemFireUtils.isNonLocalClientRegion(sessionsRegion)) {
this.registerInterestEnabled = true;
sessionsRegionAttributesMutator.addCacheListener(newSessionIdInterestRegistrar());
}
});
return sessionsRegion;
}
/**
* Constructs a new instance of {@link SessionEventHandlerCacheListenerAdapter}.
*
* @return a new instance of {@link SessionEventHandlerCacheListenerAdapter}.
* @see SessionEventHandlerCacheListenerAdapter
*/
protected SessionEventHandlerCacheListenerAdapter newSessionEventHandler() {
return new SessionEventHandlerCacheListenerAdapter(this);
}
/**
* Constructs a new instance of {@link SessionIdInterestRegisteringCacheListener}.
*
* @return a new instance of {@link SessionIdInterestRegisteringCacheListener}.
* @see SessionIdInterestRegisteringCacheListener
*/
protected SessionIdInterestRegisteringCacheListener newSessionIdInterestRegistrar() {
return new SessionIdInterestRegisteringCacheListener(this);
}
/**
@@ -156,15 +245,15 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
}
/**
* Sets the ApplicationEventPublisher used to publish Session events corresponding to
* Pivotal GemFire cache events.
* Sets the configured {@link ApplicationEventPublisher} used to publish {@link Session}
* {@link AbstractSessionEvent events} corresponding to Apache Geode/Pivotal GemFire cache events.
*
* @param applicationEventPublisher the Spring ApplicationEventPublisher used to
* publish Session-based events; must not be {@literal null}.
* @param applicationEventPublisher {@link ApplicationEventPublisher} used to publish {@link Session}-based events;
* must not be {@literal null}.
* @throws IllegalArgumentException if {@link ApplicationEventPublisher} is {@literal null}.
* @see org.springframework.context.ApplicationEventPublisher
*/
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
public void setApplicationEventPublisher(@NonNull ApplicationEventPublisher applicationEventPublisher) {
Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher is required");
@@ -172,24 +261,37 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
}
/**
* Returns a reference to the {@link ApplicationEventPublisher} used to publish {@link Session} events
* corresponding to GemFire/Geode cache events.
* Returns a reference to the configured {@link ApplicationEventPublisher} used to publish {@link Session}
* {@link AbstractSessionEvent events} corresponding to Apache Geode/Pivotal GemFire cache events.
*
* @return the Spring {@link ApplicationEventPublisher} used to publish {@link Session} events.
* @return the configured {@link ApplicationEventPublisher} used to publish {@link Session}
* {@link AbstractSessionEvent events}.
* @see org.springframework.context.ApplicationEventPublisher
*/
protected ApplicationEventPublisher getApplicationEventPublisher() {
protected @NonNull ApplicationEventPublisher getApplicationEventPublisher() {
return this.applicationEventPublisher;
}
/**
* Returns the fully-qualified name of the cache {@link Region} used to store and manage {@link Session} state.
* Returns the {@link String fully-qualified name} of the cache {@link Region} used to store
* and manage {@link Session} state.
*
* @return a {@link String} containing the fully qualified name of the cache {@link Region}
* used to store and manage {@link Session} data.
* @see #getSessionsRegion()
*/
protected String getFullyQualifiedRegionName() {
return this.fullyQualifiedRegionName;
return getSessionsRegion().getFullPath();
}
/**
* Determines whether {@link Region} {@literal register interest} is enabled
* in the current Apache Geode / Pivotal GemFire configuration.
*
* @return a boolean value indicating whether interest registration is enabled.
*/
protected boolean isRegisterInterestEnabled() {
return this.registerInterestEnabled;
}
/**
@@ -254,17 +356,43 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
.orElse(0);
}
protected Optional<SessionEventHandlerCacheListenerAdapter> getSessionEventHandler() {
return Optional.ofNullable(this.sessionEventHandler);
}
/**
* Gets a reference to the {@link GemfireOperations template} used to perform data access operations
* and other interactions on the cache {@link Region} backing this {@link SessionRepository}.
* Returns a reference to the configured Apache Geode / Pivotal GemFire cache {@link Region} used to
* store and manage (HTTP) {@link Session} data.
*
* @return a reference to the {@link GemfireOperations template} used to interact with GemFire/Geode.
* @return a reference to the configured {@link Session Sessions} {@link Region}.
* @see org.springframework.session.Session
* @see org.apache.geode.cache.Region
*/
protected @NonNull Region<Object, Session> getSessionsRegion() {
return this.sessions;
}
/**
* Returns a reference to the {@link GemfireOperations template} used to perform data access operations
* and other interactions on the cache {@link Region} storing and managing {@link Session} state
* and backing this {@link SessionRepository}.
*
* @return a reference to the {@link GemfireOperations template} used to interact the {@link Region}
* storing and managing {@link Session} state.
* @see org.springframework.data.gemfire.GemfireOperations
*/
public GemfireOperations getTemplate() {
public @NonNull GemfireOperations getSessionsTemplate() {
return this.template;
}
/**
* @deprecated use {@link #getSessionsTemplate()}.
*/
@Deprecated
public @NonNull GemfireOperations getTemplate() {
return getSessionsTemplate();
}
/**
* Sets a condition indicating whether the DataSerialization framework has been configured.
*
@@ -283,175 +411,10 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
return usingDataSerialization.get();
}
/**
* Callback method during Spring bean initialization that will capture the fully-qualified name
* of the cache {@link Region} used to manage {@link Session} state and register this {@link SessionRepository}
* as a GemFire/Geode {@link org.apache.geode.cache.CacheListener}.
*
* Additionally, this method registers GemFire/Geode {@link Instantiator Instantiators}
* for the {@link GemFireSession} and {@link GemFireSessionAttributes} types to optimize GemFire/Geode's
* instantiation logic on deserialization using the data serialization framework when accessing the stored
* {@link Session} state.
*
* @throws Exception if an error occurs during the initialization process.
*/
public void afterPropertiesSet() throws Exception {
GemfireOperations template = getTemplate();
Assert.isInstanceOf(GemfireAccessor.class, template);
Region<Object, Session> region = ((GemfireAccessor) template).getRegion();
this.fullyQualifiedRegionName = region.getFullPath();
AttributesMutator<Object, Session> attributesMutator = region.getAttributesMutator();
attributesMutator.addCacheListener(this);
}
boolean isCreate(EntryEvent<?, ?> event) {
return isCreate(event.getOperation()) && isNotUpdate(event) && isSession(event.getNewValue());
}
private boolean isCreate(Operation operation) {
return operation.isCreate() && !Operation.LOCAL_LOAD_CREATE.equals(operation);
}
private boolean isNotUpdate(EntryEvent event) {
return isNotProxyRegion() || !this.cachedSessionIds.contains(ObjectUtils.nullSafeHashCode(event.getKey()));
}
private boolean isNotProxyRegion() {
return !isProxyRegion();
}
private boolean isProxyRegion() {
return GemFireUtils.isProxy(((GemfireAccessor) getTemplate()).getRegion());
}
/**
* Used to determine whether the application developer is storing (HTTP) Sessions with other, arbitrary
* application domain objects in the same Pivotal GemFire cache {@link Region}; crazier things have happened!
*
* @param obj {@link Object} to evaluate.
* @return a boolean value indicating whether the old/new {@link Object} from the {@link Region}
* {@link EntryEvent} is indeed a {@link Session}.
* @see org.springframework.session.Session
*/
private boolean isSession(Object obj) {
return obj instanceof Session;
}
/**
* Forgets the given {@link Object session ID}.
*
* @param sessionId {@link Object} containing the session ID to forget.
* @return a boolean value indicating whether the given session ID was even being remembered.
* @see #remember(Object)
*/
boolean forget(Object sessionId) {
return this.cachedSessionIds.remove(ObjectUtils.nullSafeHashCode(sessionId));
}
/**
* Remembers the given {@link Object session ID}.
*
* @param sessionId {@link Object} containing the session ID to remember.
* @return a boolean value whether Spring Session is interested in and will remember
* this given session ID.
* @see #forget(Object)
*/
@SuppressWarnings("all")
boolean remember(Object sessionId) {
return isProxyRegion() && this.cachedSessionIds.add(ObjectUtils.nullSafeHashCode(sessionId));
}
/**
* Casts the given {@link Object} into a {@link Session} iff the {@link Object} is a {@link Session}.
*
* Otherwise, this method attempts to use the supplied {@link String session ID} to create a {@link Session}
* containing only the ID.
*
* @param obj {@link Object} to evaluate as a {@link Session}.
* @param sessionId {@link String} containing the session ID.
* @return a {@link Session} from the given {@link Object}
* or a {@link Session} containing only the supplied {@link String session ID}.
* @throws IllegalStateException if the given {@link Object} is not a {@link Session}
* and {@link String session ID} was not supplied.
*/
Session toSession(Object obj, String sessionId) {
return obj instanceof Session
? (Session) obj
: Optional.ofNullable(sessionId)
.filter(StringUtils::hasText)
.map(SessionIdHolder::create)
.orElseThrow(() -> newIllegalStateException(
"Minimally, the session ID [%s] must be known to trigger a Session event", sessionId));
}
/**
* Callback method triggered when an entry is created in the Pivotal GemFire cache {@link Region}.
*
* @param event {@link EntryEvent} containing the details of the cache operation.
* @see org.apache.geode.cache.EntryEvent
* @see #handleCreated(String, Session)
*/
@Override
public void afterCreate(EntryEvent<Object, Session> event) {
Optional.ofNullable(event)
.filter(this::isCreate)
.ifPresent(it -> {
String sessionId = it.getKey().toString();
handleCreated(sessionId, toSession(it.getNewValue(), sessionId));
});
}
/**
* Callback method triggered when an entry is destroyed in the Pivotal GemFire cache {@link Region}.
*
* @param event {@link EntryEvent} containing the details of the cache operation.
* @see org.apache.geode.cache.EntryEvent
* @see #handleDestroyed(String, Session)
*/
@Override
public void afterDestroy(EntryEvent<Object, Session> event) {
Optional.ofNullable(event)
.ifPresent(it -> {
String sessionId = event.getKey().toString();
handleDestroyed(sessionId, toSession(event.getOldValue(), sessionId));
});
}
/**
* Callback method triggered when an entry is invalidated in the Pivotal GemFire cache {@link Region}.
*
* @param event {@link EntryEvent} containing the details of the cache operation.
* @see org.apache.geode.cache.EntryEvent
* @see #handleExpired(String, Session)
*/
@Override
public void afterInvalidate(EntryEvent<Object, Session> event) {
Optional.ofNullable(event)
.ifPresent(it -> {
String sessionId = event.getKey().toString();
handleExpired(sessionId, toSession(event.getOldValue(), sessionId));
});
}
/**
* Commits the given {@link Session}.
*
* @param session {@link Session} to commit, if committable.
* @param session {@link Session} to commit, iff the {@link Session} is {@literal committable}.
* @return the given {@link Session}
* @see GemFireSession#commit()
*/
@@ -468,14 +431,15 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
}
/**
* Deletes the given {@link Session} from GemFire.
* Deletes the given {@link Session} from Apache Geode / Pivotal GemFire.
*
* @param session {@link Session} to delete.
* @return {@literal null}.
* @see org.springframework.session.Session#getId()
* @see org.springframework.session.Session
* @see #deleteById(String)
*/
protected Session delete(Session session) {
protected Session delete(@NonNull Session session) {
deleteById(session.getId());
@@ -483,88 +447,27 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
}
/**
* Causes Session created events to be published to the Spring application context.
* Handles the deletion of the given {@link Session}.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionCreatedEvent
* @param sessionId {@link String} containing the {@link Session#getId()} of the given {@link Session}.
* @param session deleted {@link Session}.
* @see SessionEventHandlerCacheListenerAdapter#handleDeleted(String, Session)
* @see org.springframework.session.Session
* @see #newSessionCreatedEvent(Session)
* @see #publishEvent(ApplicationEvent)
*/
protected void handleCreated(String sessionId, Session session) {
remember(sessionId);
publishEvent(newSessionCreatedEvent(session));
}
/**
* Causes Session deleted events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionDeletedEvent
* @see org.springframework.session.Session
* @see #newSessionDeletedEvent(Session)
* @see #publishEvent(ApplicationEvent)
* @see #forget(Object)
* @see #unregisterInterest(Object)
*/
protected void handleDeleted(String sessionId, Session session) {
forget(sessionId);
publishEvent(newSessionDeletedEvent(session));
getSessionEventHandler()
.ifPresent(it -> it.handleDeleted(sessionId, session));
unregisterInterest(sessionId);
}
/**
* Causes Session destroyed events to be published to the Spring application context.
* Publishes the specified {@link ApplicationEvent} to the Spring container thereby notifying other (potentially)
* interested application components/beans.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionDestroyedEvent
* @see org.springframework.session.Session
* @see #newSessionDestroyedEvent(Session)
* @see #publishEvent(ApplicationEvent)
* @see #forget(Object)
*/
protected void handleDestroyed(String sessionId, Session session) {
forget(sessionId);
publishEvent(newSessionDestroyedEvent(session));
}
/**
* Causes Session expired events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionExpiredEvent
* @see org.springframework.session.Session
* @see #newSessionExpiredEvent(Session)
* @see #publishEvent(ApplicationEvent)
* @see #forget(Object)
*/
protected void handleExpired(String sessionId, Session session) {
forget(sessionId);
publishEvent(newSessionExpiredEvent(session));
}
private SessionCreatedEvent newSessionCreatedEvent(Session session) {
return new SessionCreatedEvent(this, session);
}
private SessionDeletedEvent newSessionDeletedEvent(Session session) {
return new SessionDeletedEvent(this, session);
}
private SessionDestroyedEvent newSessionDestroyedEvent(Session session) {
return new SessionDestroyedEvent(this, session);
}
private SessionExpiredEvent newSessionExpiredEvent(Session session) {
return new SessionExpiredEvent(this, session);
}
/**
* Publishes the specified ApplicationEvent to the Spring application context.
*
* @param event the ApplicationEvent to publish.
* @param event {@link ApplicationEvent} to publish.
* @see org.springframework.context.ApplicationEventPublisher#publishEvent(ApplicationEvent)
* @see org.springframework.context.ApplicationEvent
*/
@@ -579,20 +482,99 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
}
/**
* Updates the {@link Session#setLastAccessedTime(Instant)} property of the {@link Session}.
* Registers interest in the given {@link Session} in order to receive notifications and updates.
*
* @param session {@link Session} of interest to this application that will be registered.
* @return the given {@link Session}.
* @see org.springframework.session.Session#getId()
* @see org.springframework.session.Session
* @see #registerInterest(Object)
*/
protected Session registerInterest(@Nullable Session session) {
Optional.ofNullable(session)
.map(Session::getId)
.ifPresent(this::registerInterest);
return session;
}
/**
* Registers interest on the {@link Session#getId()} ID} of a {@link Session}.
*
* And, only registers interest in the given Session ID iff we have not already registered interest
* in this Session ID before.
*
* @param sessionId {@link Session#getId() ID} of the {@link Session} of interest to this application.
* @see org.apache.geode.cache.Region#registerInterest(Object, InterestResultPolicy, boolean, boolean)
* @see #isRegisterInterestEnabled()
*/
protected void registerInterest(Object sessionId) {
Optional.ofNullable(sessionId)
.filter(it -> this.isRegisterInterestEnabled())
.filter(SessionUtils::isValidSessionId)
.map(ObjectUtils::nullSafeHashCode)
.filter(this.interestingSessionIds::add)
.ifPresent(it ->
getSessionsRegion().registerInterest(sessionId, DEFAULT_REGISTER_INTEREST_RESULT_POLICY,
DEFAULT_REGISTER_INTEREST_DURABILITY, DEFAULT_REGISTER_INTEREST_RECEIVE_VALUES)
);
}
/**
* Updates the {@link Session#setLastAccessedTime(Instant)} property of the {@link Session}
* to the {@link Instant#now() current time}.
*
* @param session {@link Session} to touch.
* @return the {@link Session}.
* @see org.springframework.session.Session#setLastAccessedTime(Instant)
* @see org.springframework.session.Session
* @see java.time.Instant#now()
*/
protected Session touch(Session session) {
protected @NonNull Session touch(@NonNull Session session) {
session.setLastAccessedTime(Instant.now());
return session;
}
/**
* Unregisters interest in the given {@link Session} in order to stop notifications and updates.
*
* @param session {@link Session} no longer of any interest to this application that will be unregistered.
* @return the given {@link Session}.
* @see org.springframework.session.Session#getId()
* @see org.springframework.session.Session
* @see #unregisterInterest(Object)
*/
@SuppressWarnings("unused")
protected Session unregisterInterest(@Nullable Session session) {
Optional.ofNullable(session)
.map(Session::getId)
.ifPresent(this::unregisterInterest);
return session;
}
/**
* Unregisters interest on the {@link Session#getId()} ID} of a {@link Session}.
*
* @param sessionId {@link Session#getId() ID} of the {@link Session} no longer of any interest
* to this application.
* @see org.apache.geode.cache.Region#unregisterInterest(Object)
* @see #isRegisterInterestEnabled()
*/
protected void unregisterInterest(@Nullable Object sessionId) {
Optional.ofNullable(sessionId)
.filter(it -> this.isRegisterInterestEnabled())
.map(ObjectUtils::nullSafeHashCode)
.filter(this.interestingSessionIds::remove)
.ifPresent(it -> getSessionsRegion().unregisterInterest(sessionId));
}
@SuppressWarnings("unused")
public static class DeltaCapableGemFireSession
extends GemFireSession<DeltaCapableGemFireSessionAttributes> implements Delta {
@@ -1273,4 +1255,275 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
}
}
}
protected static class SessionEventHandlerCacheListenerAdapter extends CacheListenerAdapter<Object, Session> {
private final AbstractGemFireOperationsSessionRepository sessionRepository;
private final Set<Integer> cachedSessionIds = new ConcurrentSkipListSet<>();
protected SessionEventHandlerCacheListenerAdapter(AbstractGemFireOperationsSessionRepository sessionRepository) {
Assert.notNull(sessionRepository, "SessionRepository is required");
this.sessionRepository = sessionRepository;
}
protected AbstractGemFireOperationsSessionRepository getSessionRepository() {
return this.sessionRepository;
}
/**
* Callback method triggered when an entry is created in the Pivotal GemFire cache {@link Region}.
*
* @param event {@link EntryEvent} containing the details of the cache operation.
* @see org.apache.geode.cache.EntryEvent
* @see #handleCreated(String, Session)
*/
@Override
public void afterCreate(EntryEvent<Object, Session> event) {
Optional.ofNullable(event)
.filter(this::isCreate)
.ifPresent(it -> {
String sessionId = it.getKey().toString();
handleCreated(sessionId, toSession(it.getNewValue(), sessionId));
});
}
/**
* Causes Session created events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionCreatedEvent
* @see org.springframework.session.Session
* @see #newSessionCreatedEvent(Session)
* @see #publishEvent(ApplicationEvent)
*/
protected void handleCreated(String sessionId, Session session) {
remember(sessionId);
getSessionRepository().publishEvent(newSessionCreatedEvent(session));
}
private SessionCreatedEvent newSessionCreatedEvent(Session session) {
return new SessionCreatedEvent(getSessionRepository(), session);
}
/**
* Callback method triggered when an entry is destroyed in the Pivotal GemFire cache {@link Region}.
*
* @param event {@link EntryEvent} containing the details of the cache operation.
* @see org.apache.geode.cache.EntryEvent
* @see #handleDestroyed(String, Session)
*/
@Override
public void afterDestroy(EntryEvent<Object, Session> event) {
Optional.ofNullable(event)
.ifPresent(it -> {
String sessionId = event.getKey().toString();
handleDestroyed(sessionId, toSession(event.getOldValue(), sessionId));
});
}
/**
* Causes Session destroyed events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionDestroyedEvent
* @see org.springframework.session.Session
* @see #newSessionDestroyedEvent(Session)
* @see #publishEvent(ApplicationEvent)
* @see #forget(Object)
*/
protected void handleDestroyed(String sessionId, Session session) {
forget(sessionId);
getSessionRepository().publishEvent(newSessionDestroyedEvent(session));
}
private SessionDestroyedEvent newSessionDestroyedEvent(Session session) {
return new SessionDestroyedEvent(getSessionRepository(), session);
}
/**
* Callback method triggered when an entry is invalidated in the Pivotal GemFire cache {@link Region}.
*
* @param event {@link EntryEvent} containing the details of the cache operation.
* @see org.apache.geode.cache.EntryEvent
* @see #handleExpired(String, Session)
*/
@Override
public void afterInvalidate(EntryEvent<Object, Session> event) {
Optional.ofNullable(event)
.ifPresent(it -> {
String sessionId = event.getKey().toString();
handleExpired(sessionId, toSession(event.getOldValue(), sessionId));
});
}
/**
* Causes Session expired events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionExpiredEvent
* @see org.springframework.session.Session
* @see #newSessionExpiredEvent(Session)
* @see #publishEvent(ApplicationEvent)
* @see #forget(Object)
*/
protected void handleExpired(String sessionId, Session session) {
forget(sessionId);
getSessionRepository().publishEvent(newSessionExpiredEvent(session));
}
private SessionExpiredEvent newSessionExpiredEvent(Session session) {
return new SessionExpiredEvent(getSessionRepository(), session);
}
/**
* Causes Session deleted events to be published to the Spring application context.
*
* @param sessionId a String indicating the ID of the Session.
* @param session a reference to the Session triggering the event.
* @see org.springframework.session.events.SessionDeletedEvent
* @see org.springframework.session.Session
* @see #newSessionDeletedEvent(Session)
* @see #publishEvent(ApplicationEvent)
* @see #forget(Object)
*/
protected void handleDeleted(String sessionId, Session session) {
forget(sessionId);
getSessionRepository().publishEvent(newSessionDeletedEvent(toSession(session, sessionId)));
}
private SessionDeletedEvent newSessionDeletedEvent(Session session) {
return new SessionDeletedEvent(getSessionRepository(), session);
}
boolean isCreate(EntryEvent<?, ?> event) {
return isCreate(event.getOperation()) && isNotUpdate(event) && isSession(event.getNewValue());
}
private boolean isCreate(Operation operation) {
return operation.isCreate() && !Operation.LOCAL_LOAD_CREATE.equals(operation);
}
private boolean isNotUpdate(EntryEvent event) {
return isNotProxyRegion() || !this.cachedSessionIds.contains(ObjectUtils.nullSafeHashCode(event.getKey()));
}
private boolean isNotProxyRegion() {
return !isProxyRegion();
}
private boolean isProxyRegion() {
return GemFireUtils.isProxy(getSessionRepository().getSessionsRegion());
}
/**
* Used to determine whether the application developer is storing (HTTP) {@link Session Sessions} with other,
* arbitrary application domain objects in the same Apache Geode / Pivotal GemFire cache {@link Region};
* crazier things have happened!
*
* @param obj {@link Object} to evaluate.
* @return a boolean value indicating whether the old/new {@link Object} from the {@link Region}
* {@link EntryEvent} is indeed a {@link Session}.
* @see org.springframework.session.Session
*/
private boolean isSession(Object obj) {
return obj instanceof Session;
}
/**
* Forgets the given {@link Object Session ID}.
*
* @param sessionId {@link Object} containing the Session ID to forget.
* @return a boolean value indicating whether the given Session ID was even being remembered.
* @see #remember(Object)
*/
boolean forget(Object sessionId) {
return this.cachedSessionIds.remove(ObjectUtils.nullSafeHashCode(sessionId));
}
/**
* Remembers the given {@link Object Session ID}.
*
* @param sessionId {@link Object} containing the Session ID to remember.
* @return a boolean value indicating whether Spring Session is interested in
* and will remember the given Session ID.
* @see #forget(Object)
*/
boolean remember(Object sessionId) {
return isProxyRegion() && this.cachedSessionIds.add(ObjectUtils.nullSafeHashCode(sessionId));
}
/**
* Casts the given {@link Object} into a {@link Session} iff the {@link Object} is a {@link Session}.
*
* Otherwise, this method attempts to use the supplied {@link String Session ID} to create a {@link Session}
* representation containing only the ID.
*
* @param obj {@link Object} to evaluate as a {@link Session}.
* @param sessionId {@link String} containing the Session ID.
* @return a {@link Session} from the given {@link Object} or a {@link Session} representation
* containing only the supplied {@link String Session ID}.
* @throws IllegalStateException if the given {@link Object} is not a {@link Session}
* and a {@link String Session ID} was not supplied.
*/
Session toSession(@Nullable Object obj, String sessionId) {
return obj instanceof Session
? (Session) obj
: Optional.ofNullable(sessionId)
.filter(StringUtils::hasText)
.map(SessionIdHolder::create)
.orElseThrow(() -> newIllegalStateException(
"Minimally, the Session ID [%s] must be known to trigger a Session event", sessionId));
}
}
protected static class SessionIdInterestRegisteringCacheListener extends CacheListenerAdapter<Object, Session> {
private final AbstractGemFireOperationsSessionRepository sessionRepository;
public SessionIdInterestRegisteringCacheListener(AbstractGemFireOperationsSessionRepository sessionRepository) {
Assert.notNull(sessionRepository, "SessionRepository is required");
this.sessionRepository = sessionRepository;
}
protected AbstractGemFireOperationsSessionRepository getSessionRepository() {
return this.sessionRepository;
}
@Override
public void afterCreate(EntryEvent<Object, Session> event) {
getSessionRepository().registerInterest(event.getKey());
}
@Override
public void afterDestroy(EntryEvent<Object, Session> event) {
getSessionRepository().unregisterInterest(event.getKey());
}
@Override
public void afterInvalidate(EntryEvent<Object, Session> event) {
getSessionRepository().unregisterInterest(event.getKey());
}
}
}

View File

@@ -76,52 +76,58 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
}
/**
* Gets a copy of an existing, non-expired {@link Session} by ID.
* Finds an existing, non-expired {@link Session} by ID.
*
* If the {@link Session} is expired, then the {@link Session }is deleted.
* If the {@link Session} is expired, then the {@link Session} is deleted and {@literal null} is returned.
*
* @param sessionId a String indicating the ID of the Session to get.
* @return an existing {@link Session} by ID or null if no {@link Session} exists.
* @param sessionId {@link String} containing the {@link Session#getId() ID}} of the {@link Session} to get.
* @return an existing {@link Session} by ID or {@literal null} if no {@link Session} exists
* or the {@link Session} expired.
* @see AbstractGemFireOperationsSessionRepository.GemFireSession#from(Session)
* @see org.springframework.session.Session
* @see #commit(Session)
* @see #deleteById(String)
* @see #registerInterest(Session)
* @see #touch(Session)
*/
@Nullable
public Session findById(String sessionId) {
Session storedSession = getTemplate().get(sessionId);
Session storedSession = getSessionsTemplate().get(sessionId);
if (storedSession != null) {
storedSession = storedSession.isExpired()
? delete(storedSession)
: touch(commit(GemFireSession.from(storedSession)));
: registerInterest(touch(commit(GemFireSession.from(storedSession))));
}
return storedSession;
}
/**
* Looks up all available Sessions with the particular attribute indexed by name
* having the given value.
* Finds all available {@link Session Sessions} with the particular attribute indexed by {@link String name}
* having the given {@link Object value}.
*
* @param indexName name of the indexed Session attribute. (e.g.
* {@link org.springframework.session.FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}
* ).
* @param indexValue value of the indexed Session attribute to search on (e.g.
* username).
* @return a mapping of Session ID to Session instances.
* @param indexName {@link String name} of the indexed {@link Session} attribute.
* (e.g. {@link org.springframework.session.FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}).
* @param indexValue {@link Object value} of the indexed {@link Session} attribute to search on
* (e.g. {@literal username}).
* @return a mapping of {@link Session#getId()} Session IDs} to {@link Session} objects.
* @see org.springframework.session.Session
* @see java.util.Map
* @see #prepareQuery(String)
* @see java.util.Map
* @see #commit(Session)
* @see #registerInterest(Session)
* @see #touch(Session)
*/
@Override
public Map<String, Session> findByIndexNameAndIndexValue(String indexName, String indexValue) {
SelectResults<Session> results = getTemplate().find(prepareQuery(indexName), indexValue);
SelectResults<Session> results = getSessionsTemplate().find(prepareQuery(indexName), indexValue);
Map<String, Session> sessions = new HashMap<>(results.size());
results.asList().forEach(session -> sessions.put(session.getId(), session));
results.asList().forEach(session -> sessions.put(session.getId(), registerInterest(touch(commit(session)))));
return sessions;
}
@@ -136,9 +142,11 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
*/
protected String prepareQuery(String indexName) {
String fullyQualifiedRegionName = getFullyQualifiedRegionName();
return PRINCIPAL_NAME_INDEX_NAME.equals(indexName)
? String.format(FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName())
: String.format(FIND_SESSIONS_BY_INDEX_NAME_AND_INDEX_VALUE_QUERY, getFullyQualifiedRegionName(), indexName);
? String.format(FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, fullyQualifiedRegionName)
: String.format(FIND_SESSIONS_BY_INDEX_NAME_AND_INDEX_VALUE_QUERY, fullyQualifiedRegionName, indexName);
}
/**
@@ -170,7 +178,7 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
void doSave(@NonNull Session session) {
// Save Session As GemFireSession
getTemplate().put(session.getId(), GemFireSession.from(session));
getSessionsTemplate().put(session.getId(), GemFireSession.from(session));
// Commit Session
commit(session);
@@ -185,6 +193,6 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
* @see #handleDeleted(String, Session)
*/
public void deleteById(String sessionId) {
handleDeleted(sessionId, toSession(getTemplate().<Object, Session>remove(sessionId), sessionId));
handleDeleted(sessionId, getSessionsTemplate().<Object, Session>remove(sessionId));
}
}

View File

@@ -22,17 +22,16 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import static org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
import static org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession;
@@ -41,11 +40,9 @@ import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,8 +58,9 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.gemfire.GemfireAccessor;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.data.gemfire.util.RegionUtils;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.SessionEventHandlerCacheListenerAdapter;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.session.events.SessionDeletedEvent;
@@ -97,102 +95,172 @@ public class GemFireOperationsSessionRepositoryTests {
@Mock
private ApplicationEventPublisher mockApplicationEventPublisher;
@Mock
private AttributesMutator<Object, Session> mockAttributesMutator;
@Mock
private GemfireOperationsAccessor mockTemplate;
// Subject Under Test (SUT)
private GemFireOperationsSessionRepository sessionRepository;
@Mock
private Region<Object, Session> mockRegion;
@Before
@SuppressWarnings("unchecked")
public void setup() throws Exception {
when(this.mockRegion.getAttributesMutator()).thenReturn(this.mockAttributesMutator);
when(this.mockRegion.getFullPath()).thenReturn(GemFireUtils.toRegionPath("Example"));
when(this.mockTemplate.<Object, Session>getRegion()).thenReturn(this.mockRegion);
AttributesMutator<Object, Session> mockAttributesMutator = mock(AttributesMutator.class);
this.sessionRepository = spy(new GemFireOperationsSessionRepository(this.mockTemplate));
Region<Object, Session> mockRegion = mock(Region.class);
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
when(mockRegion.getFullPath()).thenReturn(RegionUtils.toRegionPath("Example"));
doReturn(mockRegion).when(this.mockTemplate).<Object, Session>getRegion();
this.sessionRepository = new GemFireOperationsSessionRepository(this.mockTemplate);
this.sessionRepository.setApplicationEventPublisher(this.mockApplicationEventPublisher);
this.sessionRepository.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
this.sessionRepository.setUseDataSerialization(false);
this.sessionRepository.afterPropertiesSet();
assertThat(this.sessionRepository.getApplicationEventPublisher()).isSameAs(this.mockApplicationEventPublisher);
assertThat(this.sessionRepository.getFullyQualifiedRegionName()).isEqualTo(GemFireUtils.toRegionPath("Example"));
assertThat(this.sessionRepository.getFullyQualifiedRegionName()).isEqualTo(RegionUtils.toRegionPath("Example"));
assertThat(this.sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
assertThat(this.sessionRepository.getTemplate()).isSameAs(this.mockTemplate);
assertThat(this.sessionRepository.getSessionEventHandler().orElse(null)).isInstanceOf(SessionEventHandlerCacheListenerAdapter.class);
assertThat(this.sessionRepository.getSessionsRegion()).isSameAs(mockRegion);
assertThat(this.sessionRepository.getSessionsTemplate()).isSameAs(this.mockTemplate);
assertThat(GemFireOperationsSessionRepository.isUsingDataSerialization()).isFalse();
}
private Session mockSession() {
String sessionId = UUID.randomUUID().toString();
Instant now = Instant.now();
Duration maxInactiveInterval = Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
Session mockSession = mock(Session.class, withSettings().name(sessionId).lenient());
when(mockSession.getId()).thenReturn(sessionId);
when(mockSession.getAttributeNames()).thenReturn(Collections.emptySet());
when(mockSession.getCreationTime()).thenReturn(now);
when(mockSession.getLastAccessedTime()).thenReturn(now);
when(mockSession.getMaxInactiveInterval()).thenReturn(maxInactiveInterval);
return mockSession;
verify(mockAttributesMutator).addCacheListener(isA(SessionEventHandlerCacheListenerAdapter.class));
verify(mockRegion, times(1)).getAttributesMutator();
verify(this.mockTemplate, times(1)).getRegion();
}
private GemFireSession newNonDirtyGemFireSession() {
GemFireSession session = GemFireSession.create();
session.commit();;
session.commit();
return session;
}
@After
public void tearDown() {
@Test
@SuppressWarnings("unchecked")
public void constructGemFireOperationSessionRepositoryWithTemplate() {
verify(this.mockAttributesMutator, times(1)).addCacheListener(same(this.sessionRepository));
verify(this.mockRegion, times(1)).getFullPath();
verify(this.mockTemplate, times(1)).getRegion();
AttributesMutator<Object, Session> mockAttributesMutator = mock(AttributesMutator.class);
Region<Object, Session> mockRegion = mock(Region.class);
GemfireOperationsAccessor mockTemplate = mock(GemfireOperationsAccessor.class);
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
doReturn(mockRegion).when(mockTemplate).getRegion();
GemFireOperationsSessionRepository sessionRepository =
new GemFireOperationsSessionRepository(mockTemplate);
assertThat(sessionRepository).isNotNull();
assertThat(sessionRepository.getSessionsRegion()).isSameAs(mockRegion);
assertThat(sessionRepository.getSessionsTemplate()).isSameAs(mockTemplate);
verify(mockTemplate, times(1)).getRegion();
verify(mockRegion, times(1)).getAttributesMutator();
verify(mockAttributesMutator, times(1))
.addCacheListener(isA(SessionEventHandlerCacheListenerAdapter.class));
verifyNoMoreInteractions(mockAttributesMutator);
}
@Test
public void createProperlyInitializedSession() {
Instant beforeOrAtCreationTime = Instant.now();
Instant beforeCreationTime = Instant.now();
Session session = this.sessionRepository.createSession();
assertThat(session).isInstanceOf(AbstractGemFireOperationsSessionRepository.GemFireSession.class);
assertThat(session.getId()).isNotNull();
assertThat(session.getId()).isNotEmpty();
assertThat(session.getAttributeNames()).isEmpty();
assertThat(session.getCreationTime().compareTo(beforeOrAtCreationTime)).isGreaterThanOrEqualTo(0);
assertThat(session.getLastAccessedTime().compareTo(beforeOrAtCreationTime)).isGreaterThanOrEqualTo(0);
assertThat(session.getCreationTime()).isAfterOrEqualTo(beforeCreationTime);
assertThat(session.getCreationTime()).isBeforeOrEqualTo(Instant.now());
assertThat(session.isExpired()).isFalse();
assertThat(session.getLastAccessedTime()).isEqualTo(session.getCreationTime());
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
}
@Test
public void createProperlyInitializedDeltaAwareSession() {
Instant beforeCreationTime = Instant.now();
this.sessionRepository.setUseDataSerialization(true);
Session session = this.sessionRepository.createSession();
assertThat(session).isInstanceOf(AbstractGemFireOperationsSessionRepository.DeltaCapableGemFireSession.class);
assertThat(session.getId()).isNotEmpty();
assertThat(session.getAttributeNames()).isEmpty();
assertThat(session.getCreationTime()).isAfterOrEqualTo(beforeCreationTime);
assertThat(session.getCreationTime()).isBeforeOrEqualTo(Instant.now());
assertThat(session.isExpired()).isFalse();
assertThat(session.getLastAccessedTime()).isEqualTo(session.getCreationTime());
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
}
@Test
public void findByIdReturnsMatchingNonExpiredSession() {
Instant expectedCreationTime = Instant.now();
Instant currentLastAccessedTime = expectedCreationTime.plusMillis(TimeUnit.MINUTES.toMillis(5L));
Session mockSession = mock(Session.class);
when(mockSession.isExpired()).thenReturn(false);
when(mockSession.getId()).thenReturn("1");
when(mockSession.getCreationTime()).thenReturn(expectedCreationTime);
when(mockSession.getLastAccessedTime()).thenReturn(currentLastAccessedTime);
when(mockSession.getAttributeNames()).thenReturn(Collections.singleton("attributeOne"));
when(mockSession.getAttribute(eq("attributeOne"))).thenReturn("test");
when(this.mockTemplate.get(eq("1"))).thenReturn(mockSession);
GemFireOperationsSessionRepository sessionRepositorySpy = spy(this.sessionRepository);
Session actualSession = sessionRepositorySpy.findById("1");
assertThat(actualSession).isNotNull();
assertThat(actualSession).isNotSameAs(mockSession);
assertThat(actualSession.getId()).isEqualTo("1");
assertThat(actualSession.getCreationTime()).isEqualTo(expectedCreationTime);
assertThat(actualSession.getLastAccessedTime()).isNotEqualTo(currentLastAccessedTime);
assertThat(actualSession.getLastAccessedTime()).isAfterOrEqualTo(expectedCreationTime);
assertThat(actualSession.getLastAccessedTime()).isBeforeOrEqualTo(Instant.now());
assertThat(actualSession.getAttributeNames()).containsExactly("attributeOne");
assertThat(actualSession.<String>getAttribute("attributeOne")).isEqualTo("test");
verify(this.mockTemplate, times(1)).get(eq("1"));
verify(mockSession, times(1)).isExpired();
verify(mockSession, times(1)).getId();
verify(mockSession, times(1)).getCreationTime();
verify(mockSession, times(1)).getLastAccessedTime();
verify(mockSession, times(1)).getAttributeNames();
verify(mockSession, times(1)).getAttribute(eq("attributeOne"));
InOrder inOrder = inOrder(sessionRepositorySpy);
inOrder.verify(sessionRepositorySpy, times(1)).commit(eq(actualSession));
inOrder.verify(sessionRepositorySpy, times(1)).touch(eq(actualSession));
inOrder.verify(sessionRepositorySpy, times(1)).registerInterest(eq(actualSession));
}
@Test
public void findByIdDeletesMatchingExpiredSessionReturnsNull() {
String expectedSessionId = "1";
Session mockSession = mock(Session.class);
given(mockSession.isExpired()).willReturn(true);
given(mockSession.getId()).willReturn(expectedSessionId);
given(this.mockTemplate.get(eq(expectedSessionId))).willReturn(mockSession);
given(this.mockTemplate.remove(eq(expectedSessionId))).willReturn(mockSession);
when(mockSession.getId()).thenReturn("1");
when(mockSession.isExpired()).thenReturn(true);
when(this.mockTemplate.get(eq("1"))).thenReturn(mockSession);
when(this.mockTemplate.remove(eq("1"))).thenReturn(mockSession);
willAnswer(invocation -> {
doAnswer(invocation -> {
ApplicationEvent applicationEvent = invocation.getArgument(0);
@@ -201,83 +269,49 @@ public class GemFireOperationsSessionRepositoryTests {
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
assertThat(sessionEvent.<Session>getSession()).isSameAs(mockSession);
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
assertThat(sessionEvent.getSource())
.isSameAs(GemFireOperationsSessionRepositoryTests.this.sessionRepository);
assertThat(sessionEvent.getSessionId()).isEqualTo("1");
assertThat(sessionEvent.getSource()).isSameAs(this.sessionRepository);
return null;
}).given(this.mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class));
}).when(this.mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class));
assertThat(this.sessionRepository.findById(expectedSessionId)).isNull();
assertThat(this.sessionRepository.findById("1")).isNull();
verify(this.mockTemplate, times(1)).get(eq(expectedSessionId));
verify(this.mockTemplate, times(1)).remove(eq(expectedSessionId));
verify(mockSession, times(1)).isExpired();
verify(this.mockTemplate, times(1)).get(eq("1"));
verify(this.mockTemplate, times(1)).remove(eq("1"));
verify(mockSession, times(2)).getId();
verify(mockSession, times(1)).isExpired();
verify(this.mockApplicationEventPublisher, times(1))
.publishEvent(isA(SessionDeletedEvent.class));
}
@Test
public void findByIdReturnsMatchingNonExpiredSession() {
String expectedId = "1";
Instant expectedCreationTime = Instant.now();
Instant currentLastAccessedTime = expectedCreationTime.plusMillis(TimeUnit.MINUTES.toMillis(5));
Session mockSession = mock(Session.class);
given(mockSession.isExpired()).willReturn(false);
given(mockSession.getId()).willReturn(expectedId);
given(mockSession.getCreationTime()).willReturn(expectedCreationTime);
given(mockSession.getLastAccessedTime()).willReturn(currentLastAccessedTime);
given(mockSession.getAttributeNames()).willReturn(Collections.singleton("attrOne"));
given(mockSession.getAttribute(eq("attrOne"))).willReturn("test");
given(this.mockTemplate.get(eq(expectedId))).willReturn(mockSession);
Session actualSession = this.sessionRepository.findById(expectedId);
assertThat(actualSession).isNotNull();
assertThat(actualSession).isNotSameAs(mockSession);
assertThat(actualSession.getId()).isEqualTo(expectedId);
assertThat(actualSession.getCreationTime()).isEqualTo(expectedCreationTime);
assertThat(actualSession.getLastAccessedTime()).isNotEqualTo(currentLastAccessedTime);
assertThat(actualSession.getLastAccessedTime().compareTo(expectedCreationTime)).isGreaterThanOrEqualTo(0);
assertThat(actualSession.getAttributeNames()).isEqualTo(Collections.singleton("attrOne"));
assertThat(String.valueOf(actualSession.<String>getAttribute("attrOne"))).isEqualTo("test");
verify(this.mockTemplate, times(1)).get(eq(expectedId));
verify(mockSession, times(1)).isExpired();
verify(mockSession, times(1)).getId();
verify(mockSession, times(1)).getCreationTime();
verify(mockSession, times(1)).getLastAccessedTime();
verify(mockSession, times(1)).getAttributeNames();
verify(mockSession, times(1)).getAttribute(eq("attrOne"));
}
@Test
public void findByIdReturnsNull() {
when(this.mockTemplate.get(anyString())).thenReturn(null);
assertThat(this.sessionRepository.findById("1")).isNull();
GemFireOperationsSessionRepository sessionRepositorySpy = spy(this.sessionRepository);
assertThat(sessionRepositorySpy.findById("1")).isNull();
verify(this.mockTemplate, times(1)).get(eq("1"));
verify(sessionRepositorySpy, times(1)).findById(eq("1"));
verify(sessionRepositorySpy, never()).delete(any());
verify(sessionRepositorySpy, never()).commit(any());
}
@Test
@SuppressWarnings("unchecked")
public void findByIndexNameAndIndexValueReturnsMatchingSession() {
Session mockSession = mock(Session.class, "MockSession");
Session mockSession = mock(Session.class);
given(mockSession.getId()).willReturn("1");
when(mockSession.getId()).thenReturn("1");
SelectResults<Object> mockSelectResults = mock(SelectResults.class);
given(mockSelectResults.asList()).willReturn(Collections.singletonList(mockSession));
when(mockSelectResults.asList()).thenReturn(Collections.singletonList(mockSession));
String indexName = "vip";
String indexValue = "rwinch";
@@ -286,18 +320,26 @@ public class GemFireOperationsSessionRepositoryTests {
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_INDEX_NAME_AND_INDEX_VALUE_QUERY,
this.sessionRepository.getFullyQualifiedRegionName(), indexName);
given(this.mockTemplate.find(eq(expectedQql), eq(indexValue))).willReturn(mockSelectResults);
when(this.mockTemplate.find(eq(expectedQql), eq(indexValue))).thenReturn(mockSelectResults);
GemFireOperationsSessionRepository sessionRepositorySpy = spy(this.sessionRepository);
Map<String, Session> sessions =
this.sessionRepository.findByIndexNameAndIndexValue(indexName, indexValue);
sessionRepositorySpy.findByIndexNameAndIndexValue(indexName, indexValue);
assertThat(sessions).isNotNull();
assertThat(sessions.size()).isEqualTo(1);
assertThat(sessions).hasSize(1);
assertThat(sessions.get("1")).isEqualTo(mockSession);
verify(this.mockTemplate, times(1)).find(eq(expectedQql), eq(indexValue));
verify(mockSelectResults, times(1)).asList();
verify(mockSession, times(1)).getId();
verify(mockSession, times(2)).getId();
InOrder inOrder = inOrder(sessionRepositorySpy);
inOrder.verify(sessionRepositorySpy, times(1)).commit(eq(mockSession));
inOrder.verify(sessionRepositorySpy, times(1)).touch(eq(mockSession));
inOrder.verify(sessionRepositorySpy, times(1)).registerInterest(eq(mockSession));
}
@Test
@@ -308,13 +350,13 @@ public class GemFireOperationsSessionRepositoryTests {
Session mockSessionTwo = mock(Session.class, "MockSessionTwo");
Session mockSessionThree = mock(Session.class, "MockSessionThree");
given(mockSessionOne.getId()).willReturn("1");
given(mockSessionTwo.getId()).willReturn("2");
given(mockSessionThree.getId()).willReturn("3");
when(mockSessionOne.getId()).thenReturn("1");
when(mockSessionTwo.getId()).thenReturn("2");
when(mockSessionThree.getId()).thenReturn("3");
SelectResults<Object> mockSelectResults = mock(SelectResults.class);
given(mockSelectResults.asList()).willReturn(Arrays.asList(mockSessionOne, mockSessionTwo, mockSessionThree));
when(mockSelectResults.asList()).thenReturn(Arrays.asList(mockSessionOne, mockSessionTwo, mockSessionThree));
String principalName = "jblum";
@@ -322,22 +364,36 @@ public class GemFireOperationsSessionRepositoryTests {
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
this.sessionRepository.getFullyQualifiedRegionName());
given(this.mockTemplate.find(eq(expectedOql), eq(principalName))).willReturn(mockSelectResults);
when(this.mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults);
GemFireOperationsSessionRepository sessionRepositorySpy = spy(this.sessionRepository);
Map<String, Session> sessions =
this.sessionRepository.findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
sessionRepositorySpy.findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
assertThat(sessions).isNotNull();
assertThat(sessions.size()).isEqualTo(3);
assertThat(sessions).hasSize(3);
assertThat(sessions.get("1")).isEqualTo(mockSessionOne);
assertThat(sessions.get("2")).isEqualTo(mockSessionTwo);
assertThat(sessions.get("3")).isEqualTo(mockSessionThree);
verify(this.mockTemplate, times(1)).find(eq(expectedOql), eq(principalName));
verify(mockSelectResults, times(1)).asList();
verify(mockSessionOne, times(1)).getId();
verify(mockSessionTwo, times(1)).getId();
verify(mockSessionThree, times(1)).getId();
verify(mockSessionOne, times(2)).getId();
verify(mockSessionTwo, times(2)).getId();
verify(mockSessionThree, times(2)).getId();
InOrder inOrder = inOrder(sessionRepositorySpy);
inOrder.verify(sessionRepositorySpy, times(1)).commit(eq(mockSessionOne));
inOrder.verify(sessionRepositorySpy, times(1)).touch(eq(mockSessionOne));
inOrder.verify(sessionRepositorySpy, times(1)).registerInterest(eq(mockSessionOne));
inOrder.verify(sessionRepositorySpy, times(1)).commit(eq(mockSessionTwo));
inOrder.verify(sessionRepositorySpy, times(1)).touch(eq(mockSessionTwo));
inOrder.verify(sessionRepositorySpy, times(1)).registerInterest(eq(mockSessionTwo));
inOrder.verify(sessionRepositorySpy, times(1)).commit(eq(mockSessionThree));
inOrder.verify(sessionRepositorySpy, times(1)).touch(eq(mockSessionThree));
inOrder.verify(sessionRepositorySpy, times(1)).registerInterest(eq(mockSessionThree));
}
@Test
@@ -346,7 +402,7 @@ public class GemFireOperationsSessionRepositoryTests {
SelectResults<Object> mockSelectResults = mock(SelectResults.class);
given(mockSelectResults.asList()).willReturn(Collections.emptyList());
when(mockSelectResults.asList()).thenReturn(Collections.emptyList());
String principalName = "jblum";
@@ -354,20 +410,25 @@ public class GemFireOperationsSessionRepositoryTests {
String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
this.sessionRepository.getFullyQualifiedRegionName());
given(this.mockTemplate.find(eq(expectedOql), eq(principalName))).willReturn(mockSelectResults);
when(this.mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults);
GemFireOperationsSessionRepository sessionRepositorySpy = spy(this.sessionRepository);
Map<String, Session> sessions =
this.sessionRepository.findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
sessionRepositorySpy.findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
assertThat(sessions).isNotNull();
assertThat(sessions.isEmpty()).isTrue();
assertThat(sessions).isEmpty();
verify(this.mockTemplate, times(1)).find(eq(expectedOql), eq(principalName));
verify(mockSelectResults, times(1)).asList();
verify(sessionRepositorySpy, times(1))
.findByIndexNameAndIndexValue(eq(PRINCIPAL_NAME_INDEX_NAME), eq(principalName));
verify(sessionRepositorySpy, never()).commit(any());
}
@Test
public void prepareQueryReturnsIndexNameValueOql() {
public void prepareQueryReturnsIndexNameAndIndexValueOql() {
String attributeName = "testAttributeName";
@@ -403,8 +464,6 @@ public class GemFireOperationsSessionRepositoryTests {
@Test
public void saveStoresSession() {
String expectedSessionId = "1";
Instant expectedCreationTime = Instant.now();
Instant expectedLastAccessTime = expectedCreationTime.plusMillis(TimeUnit.MINUTES.toMillis(5L));
@@ -412,19 +471,19 @@ public class GemFireOperationsSessionRepositoryTests {
Session mockSession = mock(Session.class);
when(mockSession.getId()).thenReturn(expectedSessionId);
when(mockSession.getId()).thenReturn("1");
when(mockSession.getCreationTime()).thenReturn(expectedCreationTime);
when(mockSession.getLastAccessedTime()).thenReturn(expectedLastAccessTime);
when(mockSession.getMaxInactiveInterval()).thenReturn(expectedMaxInactiveInterval);
when(mockSession.getAttributeNames()).thenReturn(Collections.emptySet());
when(this.mockTemplate.put(eq(expectedSessionId), isA(GemFireSession.class)))
when(this.mockTemplate.put(eq("1"), isA(GemFireSession.class)))
.thenAnswer(invocation -> {
Session session = invocation.getArgument(1);
assertThat(session).isNotNull();
assertThat(session.getId()).isEqualTo(expectedSessionId);
assertThat(session.getId()).isEqualTo("1");
assertThat(session.getCreationTime()).isEqualTo(expectedCreationTime);
assertThat(session.getLastAccessedTime()).isEqualTo(expectedLastAccessTime);
assertThat(session.getMaxInactiveInterval()).isEqualTo(expectedMaxInactiveInterval);
@@ -433,30 +492,36 @@ public class GemFireOperationsSessionRepositoryTests {
return null;
});
this.sessionRepository.save(mockSession);
GemFireOperationsSessionRepository sessionRepositorySpy = spy(this.sessionRepository);
sessionRepositorySpy.save(mockSession);
verify(mockSession, times(2)).getId();
verify(mockSession, times(1)).getCreationTime();
verify(mockSession, times(1)).getLastAccessedTime();
verify(mockSession, times(1)).getMaxInactiveInterval();
verify(mockSession, times(1)).getAttributeNames();
verify(this.mockTemplate, times(1)).put(eq(expectedSessionId),
verify(this.mockTemplate, times(1)).put(eq("1"),
isA(GemFireSession.class));
verify(sessionRepositorySpy, times(1)).save(eq(mockSession));
verify(sessionRepositorySpy, times(1)).commit(eq(mockSession));
}
@Test
public void saveStoresAndCommitsGemFireSession() {
GemFireSession<?> session = spy(GemFireSession.create());
GemFireSession<?> session = GemFireSession.create();
assertThat(session).isNotNull();
assertThat(session.hasDelta()).isTrue();
session = spy(session);
this.sessionRepository.save(session);
InOrder orderVerifier = inOrder(session);
orderVerifier.verify(session, times(2)).hasDelta();
orderVerifier.verify(session, times(1)).hasDelta();
orderVerifier.verify(session, times(1)).getId();
orderVerifier.verify(session, times(1)).commit();
@@ -468,14 +533,16 @@ public class GemFireOperationsSessionRepositoryTests {
@SuppressWarnings("unchecked")
public void saveWillNotStoreNonDirtyGemFireSessions() {
GemFireSession session = spy(newNonDirtyGemFireSession());
GemFireSession session = newNonDirtyGemFireSession();
assertThat(session).isNotNull();
assertThat(session.hasDelta()).isFalse();
session = spy(session);
this.sessionRepository.save(session);
verify(session, times(2)).hasDelta();
verify(session, times(1)).hasDelta();
verify(session, never()).getId();
verify(session, never()).commit();
verify(this.mockTemplate, never()).put(any(), any(GemFireSession.class));
@@ -484,14 +551,14 @@ public class GemFireOperationsSessionRepositoryTests {
@Test
public void deleteRemovesExistingSessionAndHandlesDelete() {
String expectedSessionId = "1";
AtomicBoolean methodCalled = new AtomicBoolean(false);
Session mockSession = mock(Session.class);
given(mockSession.getId()).willReturn(expectedSessionId);
given(this.mockTemplate.remove(eq(expectedSessionId))).willReturn(mockSession);
when(mockSession.getId()).thenReturn("1");
when(this.mockTemplate.remove(eq("1"))).thenReturn(mockSession);
willAnswer(invocation -> {
doAnswer(invocation -> {
ApplicationEvent applicationEvent = invocation.getArgument(0);
@@ -499,18 +566,22 @@ public class GemFireOperationsSessionRepositoryTests {
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
assertThat(sessionEvent.getSource()).isSameAs(GemFireOperationsSessionRepositoryTests.this.sessionRepository);
assertThat(sessionEvent.<Session>getSession()).isSameAs(mockSession);
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
assertThat(sessionEvent.getSessionId()).isEqualTo("1");
assertThat(sessionEvent.getSource()).isSameAs(this.sessionRepository);
methodCalled.set(true);
return null;
}).given(this.mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
}).when(this.mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
this.sessionRepository.deleteById(expectedSessionId);
this.sessionRepository.deleteById("1");
assertThat(methodCalled.get()).isTrue();
verify(mockSession, times(1)).getId();
verify(this.mockTemplate, times(1)).remove(eq(expectedSessionId));
verify(this.mockTemplate, times(1)).remove(eq("1"));
verify(this.mockApplicationEventPublisher, times(1))
.publishEvent(isA(SessionDeletedEvent.class));
}
@@ -518,14 +589,9 @@ public class GemFireOperationsSessionRepositoryTests {
@Test
public void deleteRemovesNonExistingSessionAndHandlesDelete() {
AtomicBoolean called = new AtomicBoolean(false);
AtomicBoolean methodCalled = new AtomicBoolean(false);
Session mockSession = mock(Session.class);
String expectedSessionId = "1";
when(mockSession.getId()).thenReturn(expectedSessionId);
when(this.mockTemplate.remove(anyString())).thenReturn(mockSession);
when(this.mockTemplate.remove(eq("1"))).thenReturn(null);
doAnswer(invocation -> {
@@ -538,23 +604,21 @@ public class GemFireOperationsSessionRepositoryTests {
Session session = sessionEvent.getSession();
assertThat(session).isNotNull();
assertThat(session.getId()).isEqualTo(expectedSessionId);
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
assertThat(sessionEvent.getSource())
.isSameAs(GemFireOperationsSessionRepositoryTests.this.sessionRepository);
assertThat(session.getId()).isEqualTo("1");
assertThat(sessionEvent.getSessionId()).isEqualTo("1");
assertThat(sessionEvent.getSource()).isEqualTo(this.sessionRepository);
called.set(true);
methodCalled.set(true);
return null;
}).when(this.mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
this.sessionRepository.deleteById(expectedSessionId);
this.sessionRepository.deleteById("1");
assertThat(called.get()).isTrue();
assertThat(methodCalled.get()).isTrue();
verify(mockSession, times(2)).getId();
verify(this.mockTemplate, times(1)).remove(eq(expectedSessionId));
verify(this.mockTemplate, times(1)).remove(eq("1"));
verify(this.mockApplicationEventPublisher, times(1))
.publishEvent(isA(SessionDeletedEvent.class));
}

View File

@@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -58,7 +59,6 @@ import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
import org.springframework.data.gemfire.util.ArrayUtils;
@@ -96,7 +96,7 @@ import org.springframework.util.ReflectionUtils;
public class GemFireHttpSessionConfigurationTests {
@SuppressWarnings("unchecked")
private <T> T getField(Object obj, String fieldName) {
private static <T> T getField(Object obj, String fieldName) {
try {
@@ -678,9 +678,14 @@ public class GemFireHttpSessionConfigurationTests {
}
@Test
@SuppressWarnings("unchecked")
public void createsAndInitializesSessionRepositoryBean() {
GemfireOperations mockGemfireOperations = mock(GemfireOperations.class);
Region<Object, Session> mockRegion = mock(Region.class);
GemfireTemplate mockGemfireOperations = mock(GemfireTemplate.class);
doReturn(mockRegion).when(mockGemfireOperations).getRegion();
this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(120);
@@ -688,7 +693,7 @@ public class GemFireHttpSessionConfigurationTests {
this.gemfireConfiguration.sessionRepository(mockGemfireOperations);
assertThat(sessionRepository).isNotNull();
assertThat(sessionRepository.getTemplate()).isSameAs(mockGemfireOperations);
assertThat(sessionRepository.getSessionsTemplate()).isSameAs(mockGemfireOperations);
assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(120);
}