Add generic GemFire findByIndexNameAndIndexValue support

Fixes gh-353
This commit is contained in:
John Blum
2016-02-03 23:04:20 -08:00
committed by Rob Winch
parent 7de11753a9
commit c2b407189e
13 changed files with 611 additions and 50 deletions

View File

@@ -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) {

View File

@@ -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.
*

View File

@@ -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.

View File

@@ -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;
}
}