Add generic GemFire findByIndexNameAndIndexValue support
Fixes gh-353
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user