Add generic GemFire findByIndexNameAndIndexValue support
Fixes gh-353
This commit is contained in:
@@ -257,6 +257,56 @@ Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-gemfire-p2p-xml.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsessoin-gemfire-indexing]]
|
||||
==== Using Indexes with GemFire
|
||||
|
||||
While best practices concerning the proper definition of indexes that positively impact GemFire's performance is beyond
|
||||
the scope of this document, it is important to realize that Spring Session Data GemFire creates and uses indexes to
|
||||
query and find Sessions efficiently.
|
||||
|
||||
Out-of-the-box, Spring Session Data GemFire creates 1 Hash-typed Index on the implementing Session's `principalName`
|
||||
property, which corresponds to the Session attribute...
|
||||
|
||||
----
|
||||
org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
|
||||
----
|
||||
|
||||
This enables developers using the `GemFireOperationsSessionRepository` programmatically to query and find all Sessions
|
||||
with a given principal name efficiently.
|
||||
|
||||
Additionally, Spring Session Data GemFire will create a Range-based Index on the implementing Session's Map-type
|
||||
`attributes` property (i.e. on any arbitrary Session attribute) when a developer identifies 1 or more named Session
|
||||
attributes that should be indexed by GemFire.
|
||||
|
||||
Sessions attributes to index can be specified with the `indexableSessionAttributes` attribute on the `@EnableGemFireHttpSession`
|
||||
annotation. A developer adds this annotation to their Spring application `@Configuration` class when s/he wishes to
|
||||
enable Spring Session support for HttpSession backed by GemFire.
|
||||
|
||||
For example:
|
||||
|
||||
.ApplicationConfiguration.java
|
||||
----
|
||||
@EnableGemFireHttpSession(indexableSessionAttributes = { "name1", "name2", "name3" })
|
||||
class ApplicationConfiguration {
|
||||
|
||||
// @Bean definition methods implemented here
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Only Session attribute names identified in the `@EnableGemFireHttpSession` annotation's `indexableSessionAttributes`
|
||||
attribute will have an Index defined. All other Session attributes will not be indexed.
|
||||
|
||||
However, there is one caveat. Any values stored in indexable Session attributes must implement the `java.lang.Comparable<T>`
|
||||
interface. If those object values do not implement `Comparable`, then GemFire will throw an error on startup when the
|
||||
Index is defined for Regions with persistent Session data, or when an attempt is made at runtime to assign the indexable
|
||||
Session attribute a value that is not `Comparable` and the Session is saved to GemFire.
|
||||
|
||||
NOTE: Any Session attribute that is not indexed may store non-`Comparable` values.
|
||||
|
||||
To learn more about GemFire's Range-based Indexes, see http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/creating_map_indexes.html[Creating Indexes on Map Fields].
|
||||
|
||||
To learn more about GemFire Indexing in general, see http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/query_index.html[Working with Indexes].
|
||||
|
||||
[[httpsession-how]]
|
||||
=== How HttpSession Integration Works
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.ClientCacheFactory;
|
||||
import com.gemstone.gemfire.cache.query.Index;
|
||||
import com.gemstone.gemfire.cache.server.CacheServer;
|
||||
|
||||
/**
|
||||
@@ -105,7 +106,7 @@ public class AbstractGemFireIntegrationTests {
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static List<String> createJavaProcessCommandLine(Class<? extends Object> type, String... args) {
|
||||
protected static List<String> createJavaProcessCommandLine(Class<?> type, String... args) {
|
||||
List<String> commandLine = new ArrayList<String>();
|
||||
|
||||
String javaHome = System.getProperty("java.home");
|
||||
@@ -155,7 +156,7 @@ public class AbstractGemFireIntegrationTests {
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static Process run(Class<? extends Object> type, File directory, String... args) throws IOException {
|
||||
protected static Process run(Class<?> type, File directory, String... args) throws IOException {
|
||||
return new ProcessBuilder()
|
||||
.command(createJavaProcessCommandLine(type, args))
|
||||
.directory(directory)
|
||||
@@ -318,6 +319,13 @@ public class AbstractGemFireIntegrationTests {
|
||||
assertThat(actualRegion.getAttributes().getDataPolicy()).isEqualTo(expectedDataPolicy);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected void assertIndex(Index index, String expectedExpression, String expectedFromClause) {
|
||||
assertThat(index).isNotNull();
|
||||
assertThat(index.getIndexedExpression()).isEqualTo(expectedExpression);
|
||||
assertThat(index.getFromClause()).isEqualTo(expectedFromClause);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected void assertEntryIdleTimeout(Region<?, ?> region, ExpirationAction expectedAction, int expectedTimeout) {
|
||||
assertEntryIdleTimeout(region.getAttributes().getEntryIdleTimeout(), expectedAction, expectedTimeout);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.session.data.gemfire;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
||||
import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -72,11 +73,11 @@ import com.gemstone.gemfire.pdx.PdxWriter;
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class GemFireOperationsSessionRepositoryIntegrationTests extends AbstractGemFireIntegrationTests {
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 300;
|
||||
|
||||
private static final String GEMFIRE_LOG_LEVEL = "warning";
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestPartitionedSessions";
|
||||
|
||||
SecurityContext context;
|
||||
@@ -102,11 +103,15 @@ public class GemFireOperationsSessionRepositoryIntegrationTests extends Abstract
|
||||
assertEntryIdleTimeout(sessionRegion, ExpirationAction.INVALIDATE, MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
protected Map<String, ExpiringSession> doFindByPrincipalName(String principalName) {
|
||||
return gemfireSessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
protected Map<String, ExpiringSession> doFindByIndexNameAndIndexValue(String indexName, String indexValue) {
|
||||
return gemfireSessionRepository.findByIndexNameAndIndexValue(indexName, indexValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<String, ExpiringSession> doFindByPrincipalName(String principalName) {
|
||||
return doFindByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unused", "unchecked" })
|
||||
protected Map<String, ExpiringSession> doFindByPrincipalName(String regionName, String principalName) {
|
||||
try {
|
||||
Region<String, ExpiringSession> region = gemfireCache.getRegion(regionName);
|
||||
@@ -115,7 +120,8 @@ public class GemFireOperationsSessionRepositoryIntegrationTests extends Abstract
|
||||
|
||||
QueryService queryService = region.getRegionService().getQueryService();
|
||||
|
||||
String queryString = String.format("SELECT s FROM %1$s s WHERE s.principalName = $1", region.getFullPath());
|
||||
String queryString = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
region.getFullPath());
|
||||
|
||||
Query query = queryService.newQuery(queryString);
|
||||
|
||||
@@ -140,6 +146,56 @@ public class GemFireOperationsSessionRepositoryIntegrationTests extends Abstract
|
||||
return true;
|
||||
}
|
||||
|
||||
protected ExpiringSession setAttribute(ExpiringSession session, String attributeName, Object attributeValue) {
|
||||
session.setAttribute(attributeName, attributeValue);
|
||||
return session;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSessionsByIndexedSessionAttributeNameValues() {
|
||||
ExpiringSession johnBlumSession = save(touch(setAttribute(createSession("johnBlum"), "vip", "yes")));
|
||||
ExpiringSession robWinchSession = save(touch(setAttribute(createSession("robWinch"), "vip", "yes")));
|
||||
ExpiringSession jonDoeSession = save(touch(setAttribute(createSession("jonDoe"), "vip", "no")));
|
||||
ExpiringSession pieDoeSession = save(touch(setAttribute(createSession("pieDoe"), "viper", "true")));
|
||||
ExpiringSession sourDoeSession = save(touch(createSession("sourDoe")));
|
||||
|
||||
assertThat(get(johnBlumSession.getId())).isEqualTo(johnBlumSession);
|
||||
assertThat(johnBlumSession.getAttribute("vip")).isEqualTo("yes");
|
||||
assertThat(get(robWinchSession.getId())).isEqualTo(robWinchSession);
|
||||
assertThat(robWinchSession.getAttribute("vip")).isEqualTo("yes");
|
||||
assertThat(get(jonDoeSession.getId())).isEqualTo(jonDoeSession);
|
||||
assertThat(jonDoeSession.getAttribute("vip")).isEqualTo("no");
|
||||
assertThat(get(pieDoeSession.getId())).isEqualTo(pieDoeSession);
|
||||
assertThat(pieDoeSession.getAttributeNames().contains("vip")).isFalse();
|
||||
assertThat(get(sourDoeSession.getId())).isEqualTo(sourDoeSession);
|
||||
assertThat(sourDoeSession.getAttributeNames().contains("vip")).isFalse();
|
||||
|
||||
Map<String, ExpiringSession> vipSessions = doFindByIndexNameAndIndexValue("vip", "yes");
|
||||
|
||||
assertThat(vipSessions).isNotNull();
|
||||
assertThat(vipSessions.size()).isEqualTo(2);
|
||||
assertThat(vipSessions.get(johnBlumSession.getId())).isEqualTo(johnBlumSession);
|
||||
assertThat(vipSessions.get(robWinchSession.getId())).isEqualTo(robWinchSession);
|
||||
assertThat(vipSessions.containsKey(jonDoeSession.getId()));
|
||||
assertThat(vipSessions.containsKey(pieDoeSession.getId()));
|
||||
assertThat(vipSessions.containsKey(sourDoeSession.getId()));
|
||||
|
||||
Map<String, ExpiringSession> nonVipSessions = doFindByIndexNameAndIndexValue("vip", "no");
|
||||
|
||||
assertThat(nonVipSessions).isNotNull();
|
||||
assertThat(nonVipSessions.size()).isEqualTo(1);
|
||||
assertThat(nonVipSessions.get(jonDoeSession.getId())).isEqualTo(jonDoeSession);
|
||||
assertThat(nonVipSessions.containsKey(johnBlumSession.getId()));
|
||||
assertThat(nonVipSessions.containsKey(robWinchSession.getId()));
|
||||
assertThat(nonVipSessions.containsKey(pieDoeSession.getId()));
|
||||
assertThat(nonVipSessions.containsKey(sourDoeSession.getId()));
|
||||
|
||||
Map<String, ExpiringSession> noSessions = doFindByIndexNameAndIndexValue("nonExistingAttribute", "test");
|
||||
|
||||
assertThat(noSessions).isNotNull();
|
||||
assertThat(noSessions.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSessionsByPrincipalName() {
|
||||
ExpiringSession sessionOne = save(touch(createSession("robWinch")));
|
||||
@@ -212,7 +268,7 @@ public class GemFireOperationsSessionRepositoryIntegrationTests extends Abstract
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFindAfterPrincipalRemoved() {
|
||||
public void findsNoSessionsAfterPrincipalIsRemoved() {
|
||||
String username = "doesNotFindAfterPrincipalRemoved";
|
||||
ExpiringSession session = save(touch(createSession(username)));
|
||||
session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, null);
|
||||
@@ -271,6 +327,7 @@ public class GemFireOperationsSessionRepositoryIntegrationTests extends Abstract
|
||||
|
||||
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME,
|
||||
maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||
@SuppressWarnings("unused")
|
||||
static class SpringSessionGemFireConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -288,6 +345,7 @@ public class GemFireOperationsSessionRepositoryIntegrationTests extends Abstract
|
||||
CacheFactoryBean gemfireCache() {
|
||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
||||
|
||||
gemfireCache.setClose(true);
|
||||
gemfireCache.setLazyInitialize(false);
|
||||
gemfireCache.setProperties(gemfireProperties());
|
||||
gemfireCache.setUseBeanFactoryLocator(false);
|
||||
@@ -296,7 +354,7 @@ public class GemFireOperationsSessionRepositoryIntegrationTests extends Abstract
|
||||
}
|
||||
}
|
||||
|
||||
public static class Person implements PdxSerializable {
|
||||
public static class Person implements Comparable<Person>, PdxSerializable {
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
@@ -336,6 +394,12 @@ public class GemFireOperationsSessionRepositoryIntegrationTests extends Abstract
|
||||
this.lastName = pdxReader.readString("lastName");
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public int compareTo(final Person person) {
|
||||
int compareValue = getLastName().compareTo(person.getLastName());
|
||||
return (compareValue != 0 ? compareValue : getFirstName().compareTo(person.getFirstName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.data.gemfire.config.annotation.web.http;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
|
||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.query.Index;
|
||||
import com.gemstone.gemfire.cache.query.QueryService;
|
||||
|
||||
/**
|
||||
* The GemFireHttpSessionJavaConfigurationTests class...
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@DirtiesContext
|
||||
@WebAppConfiguration
|
||||
public class GemFireHttpSessionJavaConfigurationTests extends AbstractGemFireIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private Cache gemfireCache;
|
||||
|
||||
protected <K, V> Region<K, V> assertCacheAndRegion(Cache gemfireCache, String regionName, DataPolicy dataPolicy) {
|
||||
assertThat(GemFireUtils.isPeer(gemfireCache)).isTrue();
|
||||
|
||||
Region<K, V> region = gemfireCache.getRegion(regionName);
|
||||
|
||||
assertRegion(region, regionName, dataPolicy);
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gemfireCacheConfigurationIsValid() {
|
||||
Region<Object, ExpiringSession> example = assertCacheAndRegion(gemfireCache, "Example",
|
||||
DataPolicy.REPLICATE);
|
||||
|
||||
assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 900);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyGemFireExampleCacheRegionPrincipalNameIndexWasCreatedSuccessfully() {
|
||||
Region<Object, ExpiringSession> example = assertCacheAndRegion(gemfireCache, "Example",
|
||||
DataPolicy.REPLICATE);
|
||||
|
||||
QueryService queryService = example.getRegionService().getQueryService();
|
||||
|
||||
assertThat(queryService).isNotNull();
|
||||
|
||||
Index principalNameIndex = queryService.getIndex(example, "principalNameIndex");
|
||||
|
||||
assertIndex(principalNameIndex, "principalName", example.getFullPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyGemFireExampleCacheRegionSessionAttributesIndexWasNotCreated() {
|
||||
Region<Object, ExpiringSession> example = assertCacheAndRegion(gemfireCache, "Example",
|
||||
DataPolicy.REPLICATE);
|
||||
|
||||
QueryService queryService = example.getRegionService().getQueryService();
|
||||
|
||||
assertThat(queryService).isNotNull();
|
||||
|
||||
Index sessionAttributesIndex = queryService.getIndex(example, "sessionAttributesIndex");
|
||||
|
||||
assertThat(sessionAttributesIndex).isNull();
|
||||
}
|
||||
|
||||
@EnableGemFireHttpSession(indexableSessionAttributes = {}, maxInactiveIntervalInSeconds = 900,
|
||||
regionName = "Example", serverRegionShortcut = RegionShortcut.REPLICATE)
|
||||
public static class GemFireConfiguration {
|
||||
|
||||
@Bean
|
||||
Properties gemfireProperties() {
|
||||
Properties gemfireProperties = new Properties();
|
||||
gemfireProperties.setProperty("name", GemFireHttpSessionJavaConfigurationTests.class.getName());
|
||||
gemfireProperties.setProperty("mcast-port", "0");
|
||||
gemfireProperties.setProperty("log-level", "warning");
|
||||
return gemfireProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
CacheFactoryBean gemfireCache() {
|
||||
CacheFactoryBean cacheFactory = new CacheFactoryBean();
|
||||
|
||||
cacheFactory.setClose(true);
|
||||
cacheFactory.setProperties(gemfireProperties());
|
||||
cacheFactory.setUseBeanFactoryLocator(false);
|
||||
|
||||
return cacheFactory;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
|
||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@@ -31,9 +32,11 @@ import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.query.Index;
|
||||
import com.gemstone.gemfire.cache.query.QueryService;
|
||||
|
||||
/**
|
||||
* The GemFireHttpSessionConfigurationXmlTests class is a test suite of test cases testing the configuration of
|
||||
* The GemFireHttpSessionXmlConfigurationTests class is a test suite of test cases testing the configuration of
|
||||
* Spring Session backed by GemFire using XML configuration meta-data.
|
||||
*
|
||||
* @author John Blum
|
||||
@@ -48,19 +51,53 @@ import com.gemstone.gemfire.cache.Region;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class GemFireHttpSessionConfigurationXmlTests extends AbstractGemFireIntegrationTests {
|
||||
public class GemFireHttpSessionXmlConfigurationTests extends AbstractGemFireIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private Cache gemfireCache;
|
||||
|
||||
protected <K, V> Region<K, V> assertCacheAndRegion(Cache gemfireCache, String regionName, DataPolicy dataPolicy) {
|
||||
assertThat(GemFireUtils.isPeer(gemfireCache)).isTrue();
|
||||
|
||||
Region<K, V> region = gemfireCache.getRegion(regionName);
|
||||
|
||||
assertRegion(region, regionName, dataPolicy);
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gemfireCacheConfigurationIsValid() {
|
||||
assertThat(gemfireCache).isNotNull();
|
||||
Region<Object, ExpiringSession> example = assertCacheAndRegion(gemfireCache, "Example", DataPolicy.NORMAL);
|
||||
|
||||
Region<Object, ExpiringSession> example = gemfireCache.getRegion("Example");
|
||||
|
||||
assertRegion(example, "Example", DataPolicy.NORMAL);
|
||||
assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 3600);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyGemFireExampleCacheRegionPrincipalNameIndexWasCreatedSuccessfully() {
|
||||
Region<Object, ExpiringSession> example = assertCacheAndRegion(gemfireCache, "Example", DataPolicy.NORMAL);
|
||||
|
||||
QueryService queryService = example.getRegionService().getQueryService();
|
||||
|
||||
assertThat(queryService).isNotNull();
|
||||
|
||||
Index principalNameIndex = queryService.getIndex(example, "principalNameIndex");
|
||||
|
||||
assertIndex(principalNameIndex, "principalName", example.getFullPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyGemFireExampleCacheRegionSessionAttributesIndexWasCreatedSuccessfully() {
|
||||
Region<Object, ExpiringSession> example = assertCacheAndRegion(gemfireCache, "Example", DataPolicy.NORMAL);
|
||||
|
||||
QueryService queryService = example.getRegionService().getQueryService();
|
||||
|
||||
assertThat(queryService).isNotNull();
|
||||
|
||||
Index sessionAttributesIndex = queryService.getIndex(example, "sessionAttributesIndex");
|
||||
|
||||
assertIndex(sessionAttributesIndex, "s.attributes['one', 'two', 'three']",
|
||||
String.format("%1$s s", example.getFullPath()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,10 +21,13 @@ import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -325,6 +328,8 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
protected static final DateFormat TO_STRING_DATE_FORMAT = new SimpleDateFormat("YYYY-MM-dd-HH-mm-ss");
|
||||
|
||||
protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
static {
|
||||
Instantiator.register(new Instantiator(GemFireSession.class, 800813552) {
|
||||
@Override public DataSerializable newInstance() {
|
||||
@@ -333,10 +338,6 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
});
|
||||
}
|
||||
|
||||
private String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private transient boolean delta = false;
|
||||
|
||||
private int maxInactiveIntervalInSeconds;
|
||||
@@ -346,6 +347,8 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
private transient final GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(this);
|
||||
|
||||
private transient final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private String id;
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@@ -426,6 +429,11 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
return sessionAttributes.getAttributeNames();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public GemFireSessionAttributes getAttributes() {
|
||||
return sessionAttributes;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized boolean isExpired() {
|
||||
long lastAccessedTime = getLastAccessedTime();
|
||||
@@ -464,21 +472,23 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void setPrincipalName(String principalName) {
|
||||
setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
setAttribute(PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized String getPrincipalName() {
|
||||
String principalName = getAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
||||
if(principalName != null) {
|
||||
return principalName;
|
||||
String principalName = getAttribute(PRINCIPAL_NAME_INDEX_NAME);
|
||||
|
||||
if (principalName == null) {
|
||||
Object authentication = getAttribute(SPRING_SECURITY_CONTEXT);
|
||||
|
||||
if (authentication != null) {
|
||||
Expression expression = parser.parseExpression("authentication?.name");
|
||||
principalName = expression.getValue(authentication, String.class);
|
||||
}
|
||||
}
|
||||
Object authentication = getAttribute(SPRING_SECURITY_CONTEXT);
|
||||
if(authentication != null) {
|
||||
Expression expression = parser.parseExpression("authentication?.name");
|
||||
return expression.getValue(authentication, String.class);
|
||||
}
|
||||
return null;
|
||||
|
||||
return principalName;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@@ -597,17 +607,20 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
}
|
||||
|
||||
/**
|
||||
* The GemFireSessionAttributes class is a container for a Session attributes that implements both
|
||||
* The GemFireSessionAttributes class is a container for Session attributes that implements both
|
||||
* the {@link DataSerializable} and {@link Delta} GemFire interfaces for efficient storage and distribution
|
||||
* (replication) in GemFire.
|
||||
* (replication) in GemFire. Additionally, GemFireSessionAttributes extends {@link AbstractMap} providing
|
||||
* {@link Map}-like behavior since attributes of a Session are effectively a name to value mapping.
|
||||
*
|
||||
* @see java.util.AbstractMap
|
||||
* @see com.gemstone.gemfire.DataSerializable
|
||||
* @see com.gemstone.gemfire.DataSerializer
|
||||
* @see com.gemstone.gemfire.Delta
|
||||
* @see com.gemstone.gemfire.Instantiator
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class GemFireSessionAttributes implements DataSerializable, Delta {
|
||||
public static class GemFireSessionAttributes extends AbstractMap<String, Object>
|
||||
implements DataSerializable, Delta {
|
||||
|
||||
protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true;
|
||||
|
||||
@@ -677,6 +690,21 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi
|
||||
return DEFAULT_ALLOW_JAVA_SERIALIZATION;
|
||||
}
|
||||
|
||||
/* (non-Javadoc); NOTE: entrySet implementation is not Thread-safe. */
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
return new AbstractSet<Entry<String, Object>>() {
|
||||
@Override public Iterator<Entry<String, Object>> iterator() {
|
||||
return Collections.unmodifiableMap(sessionAttributes).entrySet().iterator();
|
||||
}
|
||||
|
||||
@Override public int size() {
|
||||
return sessionAttributes.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void from(Session session) {
|
||||
synchronized (lock) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.session.data.gemfire;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,6 +37,10 @@ import com.gemstone.gemfire.cache.query.SelectResults;
|
||||
*/
|
||||
public class GemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository {
|
||||
|
||||
// GemFire OQL query used to lookup Sessions by arbitrary attributes.
|
||||
protected static final String FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY =
|
||||
"SELECT s FROM %1$s s WHERE s.attributes['%2$s'] = $1";
|
||||
|
||||
// GemFire OQL query used to look up Sessions by principal name.
|
||||
protected static final String FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY =
|
||||
"SELECT s FROM %1$s s WHERE s.principalName = $1";
|
||||
@@ -54,19 +57,18 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up all the available Sessions tied to the specific user identified by principal name.
|
||||
* Looks up all available Sessions with the particular attribute indexed by name having the given value.
|
||||
*
|
||||
* @param indexName the name of the indexed value (i.e. FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME).
|
||||
* @param indexValue the value of the index to search for (i.e. username) to search for all existing Spring Sessions.
|
||||
* @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.
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see java.util.Map
|
||||
* @see #prepareQuery(String)
|
||||
*/
|
||||
public Map<String, ExpiringSession> findByIndexNameAndIndexValue(String indexName, String indexValue) {
|
||||
if(!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
SelectResults<ExpiringSession> results = getTemplate().find(String.format(
|
||||
FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName()), indexValue);
|
||||
SelectResults<ExpiringSession> results = getTemplate().find(prepareQuery(indexName), indexValue);
|
||||
|
||||
Map<String, ExpiringSession> sessions = new HashMap<String, ExpiringSession>(results.size());
|
||||
|
||||
@@ -77,6 +79,18 @@ public class GemFireOperationsSessionRepository extends AbstractGemFireOperation
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the appropriate GemFire OQL query based on the indexed Session attribute name.
|
||||
*
|
||||
* @param indexName a String indicating the name of the indexed Session attribute.
|
||||
* @return an appropriate GemFire OQL statement for querying on a particular indexed Session attribute.
|
||||
*/
|
||||
protected String prepareQuery(String indexName) {
|
||||
return (PRINCIPAL_NAME_INDEX_NAME.equals(indexName)
|
||||
? String.format(FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName())
|
||||
: String.format(FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY, getFullyQualifiedRegionName(), indexName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ExpiringSession} instance backed by GemFire.
|
||||
*
|
||||
|
||||
@@ -116,6 +116,14 @@ public @interface EnableGemFireHttpSession {
|
||||
*/
|
||||
ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY;
|
||||
|
||||
/**
|
||||
* Identifies the Session attributes by name that should be indexed for query operations.
|
||||
* For instance, find all Sessions in GemFire having attribute A defined with value X.
|
||||
*
|
||||
* @return an array of Strings identifying the names of Session attributes to index.
|
||||
*/
|
||||
String[] indexableSessionAttributes() default {};
|
||||
|
||||
/**
|
||||
* Defines the maximum interval in seconds that a Session can remain inactive before it is considered expired.
|
||||
* Defaults to 1800 seconds, or 30 minutes.
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.session.data.gemfire.AbstractGemFireOperationsSession
|
||||
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
|
||||
import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean;
|
||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
@@ -68,6 +69,8 @@ import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@@ -85,6 +88,8 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
|
||||
public static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = "ClusteredSpringSessions";
|
||||
|
||||
public static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = new String[0];
|
||||
|
||||
private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
@@ -95,6 +100,8 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
|
||||
private String springSessionGemFireRegionName = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
|
||||
|
||||
private String[] indexableSessionAttributes = DEFAULT_INDEXABLE_SESSION_ATTRIBUTES;
|
||||
|
||||
/**
|
||||
* Sets a reference to the {@link ClassLoader} used to load bean definition class types in a Spring context.
|
||||
*
|
||||
@@ -133,11 +140,55 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
*
|
||||
* @return the ClientRegionShortcut used to configure the GemFire ClientCache Region.
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
* @see EnableGemFireHttpSession#clientRegionShortcut()
|
||||
*/
|
||||
protected ClientRegionShortcut getClientRegionShortcut() {
|
||||
return (clientRegionShortcut != null ? clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the names of all Session attributes that should be indexed by GemFire.
|
||||
*
|
||||
* @param indexableSessionAttributes an array of Strings indicating the names of all Session attributes
|
||||
* for which an Index will be created by GemFire.
|
||||
*/
|
||||
public void setIndexableSessionAttributes(String[] indexableSessionAttributes) {
|
||||
this.indexableSessionAttributes = indexableSessionAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of all Session attributes that should be indexed by GemFire.
|
||||
*
|
||||
* @return an array of Strings indicating the names of all Session attributes for which an Index
|
||||
* will be created by GemFire. Defaults to an empty String array if unspecified.
|
||||
* @see EnableGemFireHttpSession#indexableSessionAttributes()
|
||||
*/
|
||||
protected String[] getIndexableSessionAttributes() {
|
||||
return (indexableSessionAttributes != null ? indexableSessionAttributes : DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of all Session attributes that will be indexed by GemFire as single String value constituting
|
||||
* the Index expression of the Index definition.
|
||||
*
|
||||
* @return a String composed of all the named Session attributes on which a GemFire Index will be created
|
||||
* as an Index definition expression. If the indexable Session attributes were not specified, then the
|
||||
* wildcard ("*") is returned.
|
||||
* @see com.gemstone.gemfire.cache.query.Index#getIndexedExpression()
|
||||
*/
|
||||
protected String getIndexableSessionAttributesAsGemFireIndexExpression() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (String sessionAttribute : getIndexableSessionAttributes()) {
|
||||
builder.append(builder.length() > 0 ? ", " : "");
|
||||
builder.append(String.format("'%1$s'", sessionAttribute));
|
||||
}
|
||||
|
||||
String indexExpression = builder.toString();
|
||||
|
||||
return (indexExpression.isEmpty() ? "*" : indexExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
|
||||
*
|
||||
@@ -153,6 +204,7 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
*
|
||||
* @return an integer value specifying the maximum interval in seconds that a Session can remain inactive
|
||||
* before it is considered expired.
|
||||
* @see EnableGemFireHttpSession#maxInactiveIntervalInSeconds()
|
||||
*/
|
||||
protected int getMaxInactiveIntervalInSeconds() {
|
||||
return maxInactiveIntervalInSeconds;
|
||||
@@ -174,6 +226,7 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
*
|
||||
* @return the RegionShortcut used to configure the GemFire Cache Region.
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
* @see EnableGemFireHttpSession#serverRegionShortcut()
|
||||
*/
|
||||
protected RegionShortcut getServerRegionShortcut() {
|
||||
return (serverRegionShortcut != null ? serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT);
|
||||
@@ -195,6 +248,7 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
* @return a String specifying the name of the GemFire (Client)Cache Region
|
||||
* used to store the Session.
|
||||
* @see com.gemstone.gemfire.cache.Region#getName()
|
||||
* @see EnableGemFireHttpSession#regionName()
|
||||
*/
|
||||
protected String getSpringSessionGemFireRegionName() {
|
||||
return (StringUtils.hasText(springSessionGemFireRegionName) ? springSessionGemFireRegionName
|
||||
@@ -216,6 +270,9 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
setClientRegionShortcut(ClientRegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes.getEnum(
|
||||
"clientRegionShortcut")));
|
||||
|
||||
setIndexableSessionAttributes(enableGemFireHttpSessionAnnotationAttributes.getStringArray(
|
||||
"indexableSessionAttributes"));
|
||||
|
||||
setMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAnnotationAttributes.getNumber(
|
||||
"maxInactiveIntervalInSeconds").intValue());
|
||||
|
||||
@@ -359,7 +416,7 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
};
|
||||
|
||||
index.setCache(gemfireCache);
|
||||
index.setName("principalNameIdx");
|
||||
index.setName("principalNameIndex");
|
||||
index.setExpression("principalName");
|
||||
index.setFrom(GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName()));
|
||||
index.setOverride(true);
|
||||
@@ -368,4 +425,34 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a Spring GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions,
|
||||
* specifically on Session attributes for quick lookup and queries on Session attribute names with a given value.
|
||||
* This index will only be created on a server @{link Region}.
|
||||
*
|
||||
* @param gemfireCache a reference to the GemFire cache.
|
||||
* @return a IndexFactoryBean creating an GemFire Index on attributes of Sessions stored in the GemFire cache {@link Region}.
|
||||
* @see org.springframework.data.gemfire.IndexFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
*/
|
||||
@Bean
|
||||
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
||||
public IndexFactoryBean sessionAttributesIndex(final GemFireCache gemfireCache) {
|
||||
IndexFactoryBean index = new IndexFactoryBean() {
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
if (GemFireUtils.isPeer(gemfireCache) && !ObjectUtils.isEmpty(getIndexableSessionAttributes())) {
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
index.setCache(gemfireCache);
|
||||
index.setName("sessionAttributesIndex");
|
||||
index.setExpression(String.format("s.attributes[%1$s]", getIndexableSessionAttributesAsGemFireIndexExpression()));
|
||||
index.setFrom(String.format("%1$s s", GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName())));
|
||||
index.setOverride(true);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1268,6 +1268,55 @@ public class AbstractGemFireOperationsSessionRepositoryTest {
|
||||
verify(mockDataInput, times(1)).readUTF();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sessionAttributesEntrySetIteratesAttributeNameValues() {
|
||||
GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes();
|
||||
|
||||
sessionAttributes.setAttribute("keyOne", "valueOne");
|
||||
sessionAttributes.setAttribute("keyTwo", "valueTwo");
|
||||
|
||||
Set<Map.Entry<String, Object>> sessionAttributeEntries = sessionAttributes.entrySet();
|
||||
|
||||
assertThat(sessionAttributeEntries).isNotNull();
|
||||
assertThat(sessionAttributeEntries.size()).isEqualTo(2);
|
||||
|
||||
Set<String> expectedNames = asSet("keyOne", "keyTwo");
|
||||
Set<?> expectedValues = asSet("valueOne", "valueTwo");
|
||||
|
||||
for (Map.Entry<String, Object> entry : sessionAttributeEntries) {
|
||||
expectedNames.remove(entry.getKey());
|
||||
expectedValues.remove(entry.getValue());
|
||||
}
|
||||
|
||||
assertThat(expectedNames.isEmpty()).isTrue();
|
||||
assertThat(expectedValues.isEmpty()).isTrue();
|
||||
|
||||
sessionAttributes.setAttribute("keyThree", "valueThree");
|
||||
|
||||
assertThat(sessionAttributeEntries.size()).isEqualTo(3);
|
||||
|
||||
expectedNames = asSet("keyOne", "keyTwo");
|
||||
expectedValues = asSet("valueOne", "valueTwo");
|
||||
|
||||
for (Map.Entry<String, Object> entry : sessionAttributeEntries) {
|
||||
expectedNames.remove(entry.getKey());
|
||||
expectedValues.remove(entry.getValue());
|
||||
}
|
||||
|
||||
assertThat(expectedNames.isEmpty()).isTrue();
|
||||
assertThat(expectedValues.isEmpty()).isTrue();
|
||||
|
||||
sessionAttributes.removeAttribute("keyOne");
|
||||
sessionAttributes.removeAttribute("keyTwo");
|
||||
|
||||
assertThat(sessionAttributeEntries.size()).isEqualTo(1);
|
||||
|
||||
Map.Entry<String, ?> entry = sessionAttributeEntries.iterator().next();
|
||||
|
||||
assertThat(entry.getKey()).isEqualTo("keyThree");
|
||||
assertThat(entry.getValue()).isEqualTo("valueThree");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sessionWithAttributesAreThreadSafe() throws Throwable {
|
||||
TestFramework.runOnce(new ThreadSafeSessionTest());
|
||||
|
||||
@@ -27,6 +27,9 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
||||
import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY;
|
||||
import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY;
|
||||
import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -47,7 +50,6 @@ import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.gemfire.GemfireAccessor;
|
||||
import org.springframework.data.gemfire.GemfireOperations;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
|
||||
@@ -66,6 +68,7 @@ import com.gemstone.gemfire.cache.query.SelectResults;
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.mockito.runners.MockitoJUnitRunner
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -110,6 +113,36 @@ public class GemFireOperationsSessionRepositoryTest {
|
||||
verify(mockTemplate, times(1)).getRegion();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void findByIndexNameValueFindsMatchingSession() {
|
||||
ExpiringSession mockSession = mock(ExpiringSession.class, "MockSession");
|
||||
|
||||
when(mockSession.getId()).thenReturn("1");
|
||||
|
||||
SelectResults<Object> mockSelectResults = mock(SelectResults.class);
|
||||
|
||||
when(mockSelectResults.asList()).thenReturn(Collections.<Object>singletonList(mockSession));
|
||||
|
||||
String indexName = "vip";
|
||||
String indexValue = "rwinch";
|
||||
|
||||
String expectedQql = String.format(FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY,
|
||||
sessionRepository.getFullyQualifiedRegionName(), indexName);
|
||||
|
||||
when(mockTemplate.find(eq(expectedQql), eq(indexValue))).thenReturn(mockSelectResults);
|
||||
|
||||
Map<String, ExpiringSession> sessions = sessionRepository.findByIndexNameAndIndexValue(indexName, indexValue);
|
||||
|
||||
assertThat(sessions).isNotNull();
|
||||
assertThat(sessions.size()).isEqualTo(1);
|
||||
assertThat(sessions.get("1")).isEqualTo(mockSession);
|
||||
|
||||
verify(mockTemplate, times(1)).find(eq(expectedQql), eq(indexValue));
|
||||
verify(mockSelectResults, times(1)).asList();
|
||||
verify(mockSession, times(1)).getId();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void findByPrincipalNameFindsMatchingSessions() throws Exception {
|
||||
@@ -127,12 +160,13 @@ public class GemFireOperationsSessionRepositoryTest {
|
||||
|
||||
String principalName = "jblum";
|
||||
|
||||
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
String expectedOql = String.format(FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
sessionRepository.getFullyQualifiedRegionName());
|
||||
|
||||
when(mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults);
|
||||
|
||||
Map<String, ExpiringSession> sessions = sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
Map<String, ExpiringSession> sessions = sessionRepository.findByIndexNameAndIndexValue(
|
||||
PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
|
||||
assertThat(sessions).isNotNull();
|
||||
assertThat(sessions.size()).isEqualTo(3);
|
||||
@@ -156,12 +190,13 @@ public class GemFireOperationsSessionRepositoryTest {
|
||||
|
||||
String principalName = "jblum";
|
||||
|
||||
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
String expectedOql = String.format(FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
sessionRepository.getFullyQualifiedRegionName());
|
||||
|
||||
when(mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults);
|
||||
|
||||
Map<String, ExpiringSession> sessions = sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
Map<String, ExpiringSession> sessions = sessionRepository.findByIndexNameAndIndexValue(
|
||||
PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
|
||||
assertThat(sessions).isNotNull();
|
||||
assertThat(sessions.isEmpty()).isTrue();
|
||||
@@ -170,6 +205,25 @@ public class GemFireOperationsSessionRepositoryTest {
|
||||
verify(mockSelectResults, times(1)).asList();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareQueryReturnsPrincipalNameOql() {
|
||||
String actualQql = sessionRepository.prepareQuery(PRINCIPAL_NAME_INDEX_NAME);
|
||||
String expectedOql = String.format(FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
sessionRepository.getFullyQualifiedRegionName());
|
||||
|
||||
assertThat(actualQql).isEqualTo(expectedOql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareQueryReturnsIndexNameValueOql() {
|
||||
String attributeName = "testAttributeName";
|
||||
String actualOql = sessionRepository.prepareQuery(attributeName);
|
||||
String expectedOql = String.format(FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY,
|
||||
sessionRepository.getFullyQualifiedRegionName(), attributeName);
|
||||
|
||||
assertThat(actualOql).isEqualTo(expectedOql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createProperlyInitializedSession() {
|
||||
final long beforeOrAtCreationTime = System.currentTimeMillis();
|
||||
|
||||
@@ -47,6 +47,9 @@ import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
@@ -58,6 +61,10 @@ public class GemFireHttpSessionConfigurationTest {
|
||||
|
||||
private GemFireHttpSessionConfiguration gemfireConfiguration;
|
||||
|
||||
protected <T> T[] toArray(T... array) {
|
||||
return array;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
gemfireConfiguration = new GemFireHttpSessionConfiguration();
|
||||
@@ -91,6 +98,29 @@ public class GemFireHttpSessionConfigurationTest {
|
||||
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetIndexableSessionAttributes() {
|
||||
assertThat(gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
|
||||
|
||||
gemfireConfiguration.setIndexableSessionAttributes(toArray("one", "two", "three"));
|
||||
|
||||
assertThat(gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(toArray("one", "two", "three"));
|
||||
assertThat(gemfireConfiguration.getIndexableSessionAttributesAsGemFireIndexExpression())
|
||||
.isEqualTo("'one', 'two', 'three'");
|
||||
|
||||
gemfireConfiguration.setIndexableSessionAttributes(toArray("one"));
|
||||
|
||||
assertThat(gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(toArray("one"));
|
||||
assertThat(gemfireConfiguration.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("'one'");
|
||||
|
||||
gemfireConfiguration.setIndexableSessionAttributes(null);
|
||||
|
||||
assertThat(gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
|
||||
assertThat(gemfireConfiguration.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetMaxInactiveIntervalInSeconds() {
|
||||
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(
|
||||
@@ -160,6 +190,7 @@ public class GemFireHttpSessionConfigurationTest {
|
||||
Map<String, Object> annotationAttributes = new HashMap<String, Object>(4);
|
||||
|
||||
annotationAttributes.put("clientRegionShortcut", ClientRegionShortcut.CACHING_PROXY);
|
||||
annotationAttributes.put("indexableSessionAttributes", toArray("one", "two", "three"));
|
||||
annotationAttributes.put("maxInactiveIntervalInSeconds", 600);
|
||||
annotationAttributes.put("serverRegionShortcut", RegionShortcut.REPLICATE);
|
||||
annotationAttributes.put("regionName", "TEST");
|
||||
@@ -170,6 +201,7 @@ public class GemFireHttpSessionConfigurationTest {
|
||||
gemfireConfiguration.setImportMetadata(mockAnnotationMetadata);
|
||||
|
||||
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
|
||||
assertThat(gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(toArray("one", "two", "three"));
|
||||
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(600);
|
||||
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE);
|
||||
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("TEST");
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
">
|
||||
|
||||
<util:properties id="gemfireProperties">
|
||||
<prop key="name">GemFireHttpSessionConfigurationXmlTests</prop>
|
||||
<prop key="name">GemFireHttpSessionXmlConfigurationTests</prop>
|
||||
<prop key="mcast-port">0</prop>
|
||||
<prop key="log-level">warning</prop>
|
||||
</util:properties>
|
||||
|
||||
<gfe:cache properties-ref="gemfireProperties"/>
|
||||
<gfe:cache properties-ref="gemfireProperties" close="true"/>
|
||||
|
||||
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
|
||||
p:maxInactiveIntervalInSeconds="3600" p:springSessionGemFireRegionName="Example"
|
||||
p:indexableSessionAttributes="one, two, three" p:maxInactiveIntervalInSeconds="3600" p:springSessionGemFireRegionName="Example"
|
||||
p:serverRegionShortcut="#{T(com.gemstone.gemfire.cache.RegionShortcut).LOCAL}"/>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user