Add GemFire Support
Fixes GH PR #148 and PR #308 implementing a GemFire Adapter to support clustered HttpSessions in Spring Session. * Resolve SGF-373 - Implement a Spring Session Adapter for GemFire backing a HttpSession similar to the Redis support. * Add Spring Session annotation to enable GemFire support with @EnableGemFireHttpSession. * Add extesion of SpringHttpSessionConfiguration to configure GemFire using GemFireHttpSessionConfiguration. * Add implementation of SessionRepository to access clustered, replicated HttpSession state in GemFire with GemFireOperationsSessionRepository. * Utilize GemFire Data Serialization framework to both replicate HttpSession state information as well as handle deltas. * Utilize GemFire OQL query to lookup arbitrary Session attributes by name, and in particular the user authenticated principal name. * Implment unit and integration tests, and in particular, tests for both peer-to-peer (p2p) and client/server topologies. * Set initial Spring Data GemFire version to 1.7.2.RELEASE, which depends on Pivotal GemFire 8.1.0. * Add documentation, Javadoc and samples along with additional Integration Tests. Fixes gh-148
This commit is contained in:
@@ -0,0 +1,766 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.data.gemfire.GemfireAccessor;
|
||||
import org.springframework.data.gemfire.GemfireOperations;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.FindByPrincipalNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.DataSerializable;
|
||||
import com.gemstone.gemfire.DataSerializer;
|
||||
import com.gemstone.gemfire.Delta;
|
||||
import com.gemstone.gemfire.Instantiator;
|
||||
import com.gemstone.gemfire.InvalidDeltaException;
|
||||
import com.gemstone.gemfire.cache.EntryEvent;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.util.CacheListenerAdapter;
|
||||
|
||||
/**
|
||||
* AbstractGemFireOperationsSessionRepository is an abstract base class encapsulating functionality common
|
||||
* to all implementations that support SessionRepository operations backed by GemFire.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.InitializingBean
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
* @see org.springframework.context.ApplicationEventPublisherAware
|
||||
* @see org.springframework.data.gemfire.GemfireAccessor
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.FindByPrincipalNameSessionRepository
|
||||
* @see org.springframework.session.Session
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.util.CacheListenerAdapter
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class AbstractGemFireOperationsSessionRepository extends CacheListenerAdapter<Object, ExpiringSession>
|
||||
implements InitializingBean, FindByPrincipalNameSessionRepository<ExpiringSession>,
|
||||
ApplicationEventPublisherAware {
|
||||
|
||||
private int maxInactiveIntervalInSeconds = GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher = new ApplicationEventPublisher() {
|
||||
public void publishEvent(ApplicationEvent event) {
|
||||
}
|
||||
};
|
||||
|
||||
private final GemfireOperations template;
|
||||
|
||||
protected final Log logger = newLogger();
|
||||
|
||||
private String fullyQualifiedRegionName;
|
||||
|
||||
/**
|
||||
* Constructs an instance of AbstractGemFireOperationsSessionRepository with a required GemfireOperations instance
|
||||
* used to perform GemFire data access operations and interactions supporting the SessionRepository operations.
|
||||
*
|
||||
* @param template the GemfireOperations instance used to interact with GemFire.
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
*/
|
||||
public AbstractGemFireOperationsSessionRepository(GemfireOperations template) {
|
||||
Assert.notNull(template, "GemfireOperations must not be null");
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for testing purposes only to override the Log implementation with a mock.
|
||||
*
|
||||
* @return an instance of Log constructed from Apache commons-logging LogFactory.
|
||||
* @see org.apache.commons.logging.LogFactory#getLog(Class)
|
||||
*/
|
||||
Log newLogger() {
|
||||
return LogFactory.getLog(getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ApplicationEventPublisher used to publish Session events corresponding to GemFire cache events.
|
||||
*
|
||||
* @param applicationEventPublisher the Spring ApplicationEventPublisher used to publish Session-based events.
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
*/
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher must not be null");
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ApplicationEventPublisher used to publish Session events corresponding to GemFire cache events.
|
||||
*
|
||||
* @return the Spring ApplicationEventPublisher used to publish Session-based events.
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
*/
|
||||
protected ApplicationEventPublisher getApplicationEventPublisher() {
|
||||
return applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fully-qualified name of the GemFire cache {@link Region} used to store and manage Session data.
|
||||
*
|
||||
* @return a String indicating the fully qualified name of the GemFire cache {@link Region} used to store
|
||||
* and manage Session data.
|
||||
*/
|
||||
protected String getFullyQualifiedRegionName() {
|
||||
return fullyQualifiedRegionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
|
||||
*
|
||||
* @param maxInactiveIntervalInSeconds an integer value specifying the maximum interval in seconds that a Session
|
||||
* can remain inactive before it is considered expired.
|
||||
*/
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
|
||||
*
|
||||
* @return an integer value specifying the maximum interval in seconds that a Session can remain inactive
|
||||
* before it is considered expired.
|
||||
*/
|
||||
public int getMaxInactiveIntervalInSeconds() {
|
||||
return maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the GemfireOperations (template) used to perform data access operations
|
||||
* and other interactions on the GemFire cache {@link Region} backing this SessionRepository.
|
||||
*
|
||||
* @return a reference to the GemfireOperations used to interact with GemFire.
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
*/
|
||||
public GemfireOperations getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method during Spring bean initialization that will capture the fully-qualified name
|
||||
* of the GemFire cache {@link Region} used to manage Session state and register this SessionRepository
|
||||
* as a GemFire {@link com.gemstone.gemfire.cache.CacheListener}.
|
||||
*
|
||||
* @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, ExpiringSession> region = ((GemfireAccessor) template).getRegion();
|
||||
|
||||
fullyQualifiedRegionName = region.getFullPath();
|
||||
region.getAttributesMutator().addCacheListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method triggered when an entry is created in the GemFire cache {@link Region}.
|
||||
*
|
||||
* @param event an EntryEvent containing the details of the cache operation.
|
||||
* @see com.gemstone.gemfire.cache.EntryEvent
|
||||
* @see #handleCreated(String, ExpiringSession)
|
||||
*/
|
||||
@Override
|
||||
public void afterCreate(EntryEvent<Object, ExpiringSession> event) {
|
||||
handleCreated(event.getKey().toString(), event.getNewValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method triggered when an entry is destroyed in the GemFire cache {@link Region}.
|
||||
*
|
||||
* @param event an EntryEvent containing the details of the cache operation.
|
||||
* @see com.gemstone.gemfire.cache.EntryEvent
|
||||
* @see #handleDestroyed(String, ExpiringSession)
|
||||
*/
|
||||
@Override
|
||||
public void afterDestroy(EntryEvent<Object, ExpiringSession> event) {
|
||||
handleDestroyed(event.getKey().toString(), event.getOldValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method triggered when an entry is invalidated in the GemFire cache {@link Region}.
|
||||
*
|
||||
* @param event an EntryEvent containing the details of the cache operation.
|
||||
* @see com.gemstone.gemfire.cache.EntryEvent
|
||||
* @see #handleExpired(String, ExpiringSession)
|
||||
*/
|
||||
@Override
|
||||
public void afterInvalidate(EntryEvent<Object, ExpiringSession> event) {
|
||||
handleExpired(event.getKey().toString(), event.getOldValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.ExpiringSession
|
||||
* @see #publishEvent(ApplicationEvent)
|
||||
*/
|
||||
protected void handleCreated(String sessionId, ExpiringSession session) {
|
||||
publishEvent(session != null ? new SessionCreatedEvent(this, session)
|
||||
: new SessionCreatedEvent(this, sessionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.ExpiringSession
|
||||
* @see #publishEvent(ApplicationEvent)
|
||||
*/
|
||||
protected void handleDeleted(String sessionId, ExpiringSession session) {
|
||||
publishEvent(session != null ? new SessionDeletedEvent(this, session)
|
||||
: new SessionDeletedEvent(this, 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.ExpiringSession
|
||||
* @see #publishEvent(ApplicationEvent)
|
||||
*/
|
||||
protected void handleDestroyed(String sessionId, ExpiringSession session) {
|
||||
publishEvent(session != null ? new SessionDestroyedEvent(this, session)
|
||||
: new SessionDestroyedEvent(this, 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.ExpiringSession
|
||||
* @see #publishEvent(ApplicationEvent)
|
||||
*/
|
||||
protected void handleExpired(String sessionId, ExpiringSession session) {
|
||||
publishEvent(session != null ? new SessionExpiredEvent(this, session)
|
||||
: new SessionExpiredEvent(this, sessionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the specified ApplicationEvent to the Spring application context.
|
||||
*
|
||||
* @param event the ApplicationEvent to publish.
|
||||
* @see org.springframework.context.ApplicationEventPublisher#publishEvent(ApplicationEvent)
|
||||
* @see org.springframework.context.ApplicationEvent
|
||||
*/
|
||||
protected void publishEvent(ApplicationEvent event) {
|
||||
try {
|
||||
getApplicationEventPublisher().publishEvent(event);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error(String.format("error occurred publishing event (%1$s)", event), t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GemFireSession is a GemFire representation model of a Spring {@link ExpiringSession} for storing and accessing
|
||||
* Session state information in GemFire. This class implements GemFire's {@link DataSerializable} interface
|
||||
* to better handle replication of Session information across the GemFire cluster.
|
||||
*
|
||||
* @see java.lang.Comparable
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes
|
||||
* @see com.gemstone.gemfire.DataSerializable
|
||||
* @see com.gemstone.gemfire.DataSerializer
|
||||
* @see com.gemstone.gemfire.Delta
|
||||
* @see com.gemstone.gemfire.Instantiator
|
||||
*/
|
||||
public static class GemFireSession implements Comparable<ExpiringSession>, DataSerializable, Delta, ExpiringSession {
|
||||
|
||||
protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true;
|
||||
|
||||
protected static final DateFormat TO_STRING_DATE_FORMAT = new SimpleDateFormat("YYYY-MM-dd-HH-mm-ss");
|
||||
|
||||
static {
|
||||
Instantiator.register(new Instantiator(GemFireSession.class, 800813552) {
|
||||
@Override public DataSerializable newInstance() {
|
||||
return new GemFireSession();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private transient boolean delta = false;
|
||||
|
||||
private int maxInactiveIntervalInSeconds;
|
||||
|
||||
private long creationTime;
|
||||
private long lastAccessedTime;
|
||||
|
||||
private transient final GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(this);
|
||||
|
||||
private String id;
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected GemFireSession() {
|
||||
this(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected GemFireSession(String id) {
|
||||
this.id = validateId(id);
|
||||
this.creationTime = System.currentTimeMillis();
|
||||
this.lastAccessedTime = this.creationTime;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected GemFireSession(ExpiringSession session) {
|
||||
Assert.notNull(session, "The ExpiringSession to copy cannot be null");
|
||||
|
||||
this.id = session.getId();
|
||||
this.creationTime = session.getCreationTime();
|
||||
this.lastAccessedTime = session.getLastAccessedTime();
|
||||
this.maxInactiveIntervalInSeconds = session.getMaxInactiveIntervalInSeconds();
|
||||
this.sessionAttributes.from(session);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static GemFireSession create(int maxInactiveIntervalInSeconds) {
|
||||
GemFireSession session = new GemFireSession();
|
||||
session.setMaxInactiveIntervalInSeconds(maxInactiveIntervalInSeconds);
|
||||
return session;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static GemFireSession from(ExpiringSession expiringSession) {
|
||||
GemFireSession session = new GemFireSession(expiringSession);
|
||||
session.setLastAccessedTime(System.currentTimeMillis());
|
||||
return session;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private String validateId(String id) {
|
||||
Assert.hasText(id, "ID must be specified");
|
||||
return id;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected boolean allowJavaSerialization() {
|
||||
return DEFAULT_ALLOW_JAVA_SERIALIZATION;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
sessionAttributes.setAttribute(attributeName, attributeValue);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void removeAttribute(String attributeName) {
|
||||
sessionAttributes.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return sessionAttributes.getAttribute(attributeName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public Set<String> getAttributeNames() {
|
||||
return sessionAttributes.getAttributeNames();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized boolean isExpired() {
|
||||
long lastAccessedTime = getLastAccessedTime();
|
||||
long maxInactiveIntervalInSeconds = getMaxInactiveIntervalInSeconds();
|
||||
|
||||
return (maxInactiveIntervalInSeconds >= 0
|
||||
&& (idleTimeout(maxInactiveIntervalInSeconds) >= lastAccessedTime));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private long idleTimeout(long maxInactiveIntervalInSeconds) {
|
||||
return (System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(maxInactiveIntervalInSeconds));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void setLastAccessedTime(long lastAccessedTime) {
|
||||
this.delta |= (this.lastAccessedTime != lastAccessedTime);
|
||||
this.lastAccessedTime = lastAccessedTime;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized long getLastAccessedTime() {
|
||||
return lastAccessedTime;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void setMaxInactiveIntervalInSeconds(final int maxInactiveIntervalInSeconds) {
|
||||
this.delta |= (this.maxInactiveIntervalInSeconds != maxInactiveIntervalInSeconds);
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized int getMaxInactiveIntervalInSeconds() {
|
||||
return maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void setPrincipalName(String principalName) {
|
||||
setAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME, principalName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized String getPrincipalName() {
|
||||
return getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void toData(DataOutput out) throws IOException {
|
||||
out.writeUTF(getId());
|
||||
out.writeLong(getCreationTime());
|
||||
out.writeLong(getLastAccessedTime());
|
||||
out.writeInt(getMaxInactiveIntervalInSeconds());
|
||||
|
||||
String principalName = getPrincipalName();
|
||||
int length = (StringUtils.hasText(principalName) ? principalName.length() : 0);
|
||||
|
||||
out.writeInt(length);
|
||||
|
||||
if (length > 0) {
|
||||
out.writeUTF(principalName);
|
||||
}
|
||||
|
||||
writeObject(sessionAttributes, out);
|
||||
|
||||
this.delta = false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
void writeObject(Object obj, DataOutput out) throws IOException {
|
||||
DataSerializer.writeObject(obj, out, allowJavaSerialization());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void fromData(DataInput in) throws ClassNotFoundException, IOException {
|
||||
id = in.readUTF();
|
||||
creationTime = in.readLong();
|
||||
setLastAccessedTime(in.readLong());
|
||||
setMaxInactiveIntervalInSeconds(in.readInt());
|
||||
|
||||
int principalNameLength = in.readInt();
|
||||
|
||||
if (principalNameLength > 0) {
|
||||
setPrincipalName(in.readUTF());
|
||||
}
|
||||
|
||||
sessionAttributes.from(this.<GemFireSessionAttributes>readObject(in));
|
||||
|
||||
this.delta = false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
<T> T readObject(DataInput in) throws ClassNotFoundException, IOException {
|
||||
return DataSerializer.readObject(in);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized boolean hasDelta() {
|
||||
return (delta || sessionAttributes.hasDelta());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void toDelta(DataOutput out) throws IOException {
|
||||
out.writeLong(getLastAccessedTime());
|
||||
out.writeInt(getMaxInactiveIntervalInSeconds());
|
||||
sessionAttributes.toDelta(out);
|
||||
this.delta = false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void fromDelta(DataInput in) throws IOException {
|
||||
setLastAccessedTime(in.readLong());
|
||||
setMaxInactiveIntervalInSeconds(in.readInt());
|
||||
sessionAttributes.fromDelta(in);
|
||||
this.delta = false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("all")
|
||||
public int compareTo(ExpiringSession session) {
|
||||
return (Long.valueOf(getCreationTime()).compareTo(session.getCreationTime()));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof Session)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Session that = (Session) obj;
|
||||
|
||||
return this.getId().equals(that.getId());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashValue = 17;
|
||||
hashValue = 37 * hashValue + getId().hashCode();
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return String.format("{ @type = %1$s, id = %2$s, creationTime = %3$s, lastAccessedTime = %4$s"
|
||||
+ ", maxInactiveIntervalInSeconds = %5$s, principalName = %6$s }", getClass().getName(), getId(),
|
||||
toString(getCreationTime()), toString(getLastAccessedTime()), getMaxInactiveIntervalInSeconds(),
|
||||
getPrincipalName());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private String toString(long timestamp) {
|
||||
return TO_STRING_DATE_FORMAT.format(new Date(timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The GemFireSessionAttributes class is a container for a Session attributes that implements both
|
||||
* the {@link DataSerializable} and {@link Delta} GemFire interfaces for efficient storage and distribution
|
||||
* (replication) in GemFire.
|
||||
*
|
||||
* @see com.gemstone.gemfire.DataSerializable
|
||||
* @see com.gemstone.gemfire.DataSerializer
|
||||
* @see com.gemstone.gemfire.Delta
|
||||
* @see com.gemstone.gemfire.Instantiator
|
||||
*/
|
||||
public static class GemFireSessionAttributes implements DataSerializable, Delta {
|
||||
|
||||
protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true;
|
||||
|
||||
static {
|
||||
Instantiator.register(new Instantiator(GemFireSessionAttributes.class, 800828008) {
|
||||
@Override public DataSerializable newInstance() {
|
||||
return new GemFireSessionAttributes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private transient final Map<String, Object> sessionAttributes = new HashMap<String, Object>();
|
||||
private transient final Map<String, Object> sessionAttributeDeltas = new HashMap<String, Object>();
|
||||
|
||||
private transient final Object lock;
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected GemFireSessionAttributes() {
|
||||
this.lock = this;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected GemFireSessionAttributes(Object lock) {
|
||||
this.lock = (lock != null ? lock : this);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
synchronized (lock) {
|
||||
if (attributeValue != null) {
|
||||
if (!attributeValue.equals(sessionAttributes.put(attributeName, attributeValue))) {
|
||||
sessionAttributeDeltas.put(attributeName, attributeValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void removeAttribute(String attributeName) {
|
||||
synchronized (lock) {
|
||||
if (sessionAttributes.remove(attributeName) != null) {
|
||||
sessionAttributeDeltas.put(attributeName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
synchronized (lock) {
|
||||
return (T) sessionAttributes.get(attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public Set<String> getAttributeNames() {
|
||||
synchronized (lock) {
|
||||
return Collections.unmodifiableSet(new HashSet<String>(sessionAttributes.keySet()));
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected boolean allowJavaSerialization() {
|
||||
return DEFAULT_ALLOW_JAVA_SERIALIZATION;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void from(Session session) {
|
||||
synchronized (lock) {
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
setAttribute(attributeName, session.getAttribute(attributeName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void from(GemFireSessionAttributes sessionAttributes) {
|
||||
synchronized (lock) {
|
||||
for (String attributeName : sessionAttributes.getAttributeNames()) {
|
||||
setAttribute(attributeName, sessionAttributes.getAttribute(attributeName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void toData(DataOutput out) throws IOException {
|
||||
synchronized (lock) {
|
||||
Set<String> attributeNames = getAttributeNames();
|
||||
|
||||
out.writeInt(attributeNames.size());
|
||||
|
||||
for (String attributeName : attributeNames) {
|
||||
out.writeUTF(attributeName);
|
||||
writeObject(getAttribute(attributeName), out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
void writeObject(Object obj, DataOutput out) throws IOException {
|
||||
DataSerializer.writeObject(obj, out, allowJavaSerialization());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void fromData(DataInput in) throws IOException, ClassNotFoundException {
|
||||
synchronized (lock) {
|
||||
for (int count = in.readInt(); count > 0; count--) {
|
||||
setAttribute(in.readUTF(), readObject(in));
|
||||
}
|
||||
|
||||
sessionAttributeDeltas.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
<T> T readObject(DataInput in) throws ClassNotFoundException , IOException {
|
||||
return DataSerializer.readObject(in);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public boolean hasDelta() {
|
||||
synchronized (lock) {
|
||||
return !sessionAttributeDeltas.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void toDelta(DataOutput out) throws IOException {
|
||||
synchronized (lock) {
|
||||
out.writeInt(sessionAttributeDeltas.size());
|
||||
|
||||
for (Map.Entry<String, Object> entry : sessionAttributeDeltas.entrySet()) {
|
||||
out.writeUTF(entry.getKey());
|
||||
writeObject(entry.getValue(), out);
|
||||
}
|
||||
|
||||
sessionAttributeDeltas.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void fromDelta(DataInput in) throws InvalidDeltaException, IOException {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
int count = in.readInt();
|
||||
|
||||
Map<String, Object> deltas = new HashMap<String, Object>(count);
|
||||
|
||||
while (count-- > 0) {
|
||||
deltas.put(in.readUTF(), readObject(in));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : deltas.entrySet()) {
|
||||
setAttribute(entry.getKey(), entry.getValue());
|
||||
sessionAttributeDeltas.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new InvalidDeltaException("class type in data not found", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sessionAttributes.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.gemfire.GemfireOperations;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
|
||||
import com.gemstone.gemfire.cache.query.SelectResults;
|
||||
|
||||
/**
|
||||
* The GemFireOperationsSessionRepository class is a Spring SessionRepository implementation that interfaces with
|
||||
* and uses GemFire to back and store Spring Sessions.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.Session
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class GemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository {
|
||||
|
||||
// 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";
|
||||
|
||||
/**
|
||||
* Constructs an instance of GemFireOperationsSessionRepository initialized with the required GemfireOperations
|
||||
* object used to perform data access operations to manage Session state.
|
||||
*
|
||||
* @param template the GemfireOperations object used to access and manage Session state in GemFire.
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
*/
|
||||
public GemFireOperationsSessionRepository(GemfireOperations template) {
|
||||
super(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up all the available Sessions tied to the specific user identified by principal name.
|
||||
*
|
||||
* @param principalName the principal name (i.e. username) to search for all existing Spring Sessions.
|
||||
* @return a mapping of Session ID to Session instances.
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
*/
|
||||
public Map<String, ExpiringSession> findByPrincipalName(String principalName) {
|
||||
SelectResults<ExpiringSession> results = getTemplate().find(String.format(
|
||||
FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName()), principalName);
|
||||
|
||||
Map<String, ExpiringSession> sessions = new HashMap<String, ExpiringSession>(results.size());
|
||||
|
||||
for (ExpiringSession session : results.asList()) {
|
||||
sessions.put(session.getId(), session);
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ExpiringSession} instance backed by GemFire.
|
||||
*
|
||||
* @return an instance of {@link ExpiringSession} backed by GemFire.
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession#create(int)
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see #getMaxInactiveIntervalInSeconds()
|
||||
*/
|
||||
public ExpiringSession createSession() {
|
||||
return GemFireSession.create(getMaxInactiveIntervalInSeconds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a copy of an existing, non-expired {@link ExpiringSession} by ID. If the Session is expired,
|
||||
* then it is deleted.
|
||||
*
|
||||
* @param sessionId a String indicating the ID of the Session to get.
|
||||
* @return an existing {@link ExpiringSession} by ID or null if not Session exists.
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession#from(ExpiringSession)
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see #delete(String)
|
||||
*/
|
||||
public ExpiringSession getSession(String sessionId) {
|
||||
ExpiringSession storedSession = getTemplate().get(sessionId);
|
||||
|
||||
if (storedSession != null) {
|
||||
if (storedSession.isExpired()) {
|
||||
delete(storedSession.getId());
|
||||
}
|
||||
else {
|
||||
return GemFireSession.from(storedSession);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the specified {@link ExpiringSession} to GemFire.
|
||||
*
|
||||
* @param session the {@link ExpiringSession} to save.
|
||||
* @see org.springframework.data.gemfire.GemfireOperations#put(Object, Object)
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
*/
|
||||
public void save(ExpiringSession session) {
|
||||
getTemplate().put(session.getId(), new GemFireSession(session));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes (removes) any existing {@link ExpiringSession} from GemFire. This operation also results in
|
||||
* a SessionDeletedEvent.
|
||||
*
|
||||
* @param sessionId a String indicating the ID of the Session to remove from GemFire.
|
||||
* @see org.springframework.data.gemfire.GemfireOperations#remove(Object)
|
||||
* @see #handleDeleted(String, ExpiringSession)
|
||||
*/
|
||||
public void delete(String sessionId) {
|
||||
handleDeleted(sessionId, getTemplate().<Object, ExpiringSession>remove(sessionId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* Add this annotation to an {@code @Configuration} class to expose the SessionRepositoryFilter
|
||||
* as a bean named "springSessionRepositoryFilter" and backed by Pivotal GemFire or Apache Geode.
|
||||
*
|
||||
* In order to leverage the annotation, a single Pivotal GemFire/Apache Geode {@link com.gemstone.gemfire.cache.Cache}
|
||||
* or {@link com.gemstone.gemfire.cache.client.ClientCache} instance must be provided.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {@literal @Configuration}
|
||||
* {@literal @EnableGemFireHttpSession}
|
||||
* public class GemFirePeerCacheHttpSessionConfiguration {
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public Properties gemfireProperties() {
|
||||
* Properties gemfireProperties = new Properties();
|
||||
* gemfireProperties.setProperty("name", "ExamplePeer");
|
||||
* gemfireProperties.setProperty("mcast-port", "0");
|
||||
* gemfireProperties.setProperty("log-level", "warning");
|
||||
* return gemfireProperties;
|
||||
* }
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public CacheFactoryBean gemfireCache() throws Exception {
|
||||
* CacheFactoryBean clientCacheFactoryBean = new CacheFactoryBean();
|
||||
* clientCacheFactoryBean.setLazyInitialize(false);
|
||||
* clientCacheFactoryBean.setProperties(gemfireProperties());
|
||||
* clientCacheFactoryBean.setUseBeanFactoryLocator(false);
|
||||
* return clientCacheFactoryBean;
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* Alternatively, a Spring Session can be configured to use Pivotal GemFire (Apache Geode) as a client
|
||||
* using a dedicated GemFire Server cluster and a {@link com.gemstone.gemfire.cache.client.ClientCache}.
|
||||
* For example:
|
||||
*
|
||||
* <code>
|
||||
* {@literal @Configuration}
|
||||
* {@literal @EnableGemFireHttpSession}
|
||||
* public class GemFireClientCacheHttpSessionConfiguration {
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public Properties gemfireProperties() {
|
||||
* Properties gemfireProperties = new Properties();
|
||||
* gemfireProperties.setProperty("name", "ExampleClient");
|
||||
* gemfireProperties.setProperty("log-level", "warning");
|
||||
* return gemfireProperties;
|
||||
* }
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public ClientCacheFactoryBean gemfireCache() throws Exception {
|
||||
* ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean();
|
||||
* clientCacheFactoryBean.setLazyInitialize(false);
|
||||
* clientCacheFactoryBean.setProperties(gemfireProperties());
|
||||
* clientCacheFactoryBean.setUseBeanFactoryLocator(false);
|
||||
* return clientCacheFactoryBean;
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* More advanced configurations can extend {@link GemFireHttpSessionConfiguration} instead.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see org.springframework.session.config.annotation.web.http.EnableSpringHttpSession
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@SuppressWarnings("unused")
|
||||
@Target(ElementType.TYPE)
|
||||
@Configuration
|
||||
@Import(GemFireHttpSessionConfiguration.class)
|
||||
public @interface EnableGemFireHttpSession {
|
||||
|
||||
/**
|
||||
* Defines the GemFire ClientCache Region DataPolicy.
|
||||
*
|
||||
* @return a ClientRegionShortcut used to specify and configure the ClientCache Region DataPolicy.
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY;
|
||||
|
||||
/**
|
||||
* Defines the maximum interval in seconds that a Session can remain inactive before it is considered expired.
|
||||
* Defaults to 1800 seconds, or 30 minutes.
|
||||
*
|
||||
* @return an integer value defining the maximum inactive interval in seconds for declaring a Session expired.
|
||||
*/
|
||||
int maxInactiveIntervalInSeconds() default 1800;
|
||||
|
||||
/**
|
||||
* Defines the name of the GemFire (Client)Cache Region used to store Sessions.
|
||||
*
|
||||
* @return a String specifying the name of the GemFire (Client)Cache Region used to store Sessions.
|
||||
* @see com.gemstone.gemfire.cache.Region#getName()
|
||||
*/
|
||||
String regionName() default "ClusteredSpringSessions";
|
||||
|
||||
/**
|
||||
* Defines the GemFire, Peer Cache Region DataPolicy.
|
||||
*
|
||||
* @return a RegionShortcut used to specify and configure the Peer Cache Region DataPolicy.
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* 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 java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.GemfireOperations;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.IndexFactoryBean;
|
||||
import org.springframework.data.gemfire.IndexType;
|
||||
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession;
|
||||
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.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.ExpirationAttributes;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionAttributes;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* The GemFireHttpSessionConfiguration class is a Spring @Configuration class used to configure and initialize
|
||||
* Pivotal GemFire (or Apache Geode) as a clustered, replicated HttpSession provider implementation in Spring Session.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.BeanClassLoaderAware
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.ImportAware
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.data.gemfire.IndexFactoryBean
|
||||
* @see org.springframework.data.gemfire.RegionAttributesFactoryBean
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.ExpirationAttributes
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
implements BeanClassLoaderAware, ImportAware {
|
||||
|
||||
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS = (int) TimeUnit.MINUTES.toSeconds(30);
|
||||
|
||||
protected static final Class<Object> SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT = Object.class;
|
||||
protected static final Class<GemFireSession> SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT = GemFireSession.class;
|
||||
|
||||
public static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY;
|
||||
|
||||
public static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionShortcut.PARTITION;
|
||||
|
||||
public static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = "ClusteredSpringSessions";
|
||||
|
||||
private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT;
|
||||
|
||||
private RegionShortcut serverRegionShortcut = DEFAULT_SERVER_REGION_SHORTCUT;
|
||||
|
||||
private String springSessionGemFireRegionName = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
|
||||
|
||||
/**
|
||||
* Sets a reference to the {@link ClassLoader} used to load bean definition class types in a Spring context.
|
||||
*
|
||||
* @param beanClassLoader the ClassLoader used by the Spring container to load bean class types.
|
||||
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader)
|
||||
* @see java.lang.ClassLoader
|
||||
*/
|
||||
public void setBeanClassLoader(ClassLoader beanClassLoader) {
|
||||
this.beanClassLoader = beanClassLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the {@link ClassLoader} used to load bean definition class types in a Spring context.
|
||||
*
|
||||
* @return the ClassLoader used by the Spring container to load bean class types.
|
||||
* @see java.lang.ClassLoader
|
||||
*/
|
||||
protected ClassLoader getBeanClassLoader() {
|
||||
return beanClassLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache Region
|
||||
* that will store Spring Sessions.
|
||||
*
|
||||
* @param shortcut the ClientRegionShortcut used to configure the GemFire ClientCache Region.
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
public void setClientRegionShortcut(ClientRegionShortcut shortcut) {
|
||||
this.clientRegionShortcut = shortcut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache Region
|
||||
* that will store Spring Sessions. Defaults to {@link ClientRegionShortcut#PROXY}.
|
||||
*
|
||||
* @return the ClientRegionShortcut used to configure the GemFire ClientCache Region.
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
protected ClientRegionShortcut getClientRegionShortcut() {
|
||||
return (clientRegionShortcut != null ? clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
|
||||
*
|
||||
* @param maxInactiveIntervalInSeconds an integer value specifying the maximum interval in seconds that a Session
|
||||
* can remain inactive before it is considered expired.
|
||||
*/
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum interval in seconds in which a Session can remain inactive before it is considered expired.
|
||||
*
|
||||
* @return an integer value specifying the maximum interval in seconds that a Session can remain inactive
|
||||
* before it is considered expired.
|
||||
*/
|
||||
protected int getMaxInactiveIntervalInSeconds() {
|
||||
return maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RegionShortcut} used to configure the GemFire Cache Region that will store Spring Sessions.
|
||||
*
|
||||
* @param shortcut the RegionShortcut used to configure the GemFire Cache Region.
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
public void setServerRegionShortcut(RegionShortcut shortcut) {
|
||||
serverRegionShortcut = shortcut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link RegionShortcut} used to configure the GemFire Cache Region that will store Spring Sessions.
|
||||
* Defaults to {@link RegionShortcut#PARTITION}.
|
||||
*
|
||||
* @return the RegionShortcut used to configure the GemFire Cache Region.
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
protected RegionShortcut getServerRegionShortcut() {
|
||||
return (serverRegionShortcut != null ? serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the Gemfire (Client)Cache Region used to store Sessions.
|
||||
*
|
||||
* @param springSessionGemFireRegionName a String specifying the name of the GemFire (Client)Cache Region
|
||||
* used to store the Session.
|
||||
*/
|
||||
public void setSpringSessionGemFireRegionName(String springSessionGemFireRegionName) {
|
||||
this.springSessionGemFireRegionName = springSessionGemFireRegionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the Gemfire (Client)Cache Region used to store Sessions. Defaults to 'ClusteredSpringSessions'.
|
||||
*
|
||||
* @return a String specifying the name of the GemFire (Client)Cache Region
|
||||
* used to store the Session.
|
||||
* @see com.gemstone.gemfire.cache.Region#getName()
|
||||
*/
|
||||
protected String getSpringSessionGemFireRegionName() {
|
||||
return (StringUtils.hasText(springSessionGemFireRegionName) ? springSessionGemFireRegionName
|
||||
: DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback with the {@link AnnotationMetadata} of the class containing @Import annotation that imported
|
||||
* this @Configuration class.
|
||||
*
|
||||
* @param importMetadata the AnnotationMetadata of the class importing this @Configuration class.
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
*/
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
AnnotationAttributes enableGemFireHttpSessionAnnotationAttributes = AnnotationAttributes.fromMap(
|
||||
importMetadata.getAnnotationAttributes(EnableGemFireHttpSession.class.getName()));
|
||||
|
||||
setClientRegionShortcut(ClientRegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes.getEnum(
|
||||
"clientRegionShortcut")));
|
||||
|
||||
setMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAnnotationAttributes.getNumber(
|
||||
"maxInactiveIntervalInSeconds").intValue());
|
||||
|
||||
setServerRegionShortcut(RegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes.getEnum(
|
||||
"serverRegionShortcut")));
|
||||
|
||||
setSpringSessionGemFireRegionName(enableGemFireHttpSessionAnnotationAttributes.getString("regionName"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the Spring SessionRepository bean used to interact with GemFire as a Spring Session provider.
|
||||
*
|
||||
* @param gemfireOperations an instance of {@link GemfireOperations} used to manage Spring Sessions in GemFire.
|
||||
* @return a GemFireOperationsSessionRepository for managing (clustering/replicating) Sessions using GemFire.
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
*/
|
||||
@Bean
|
||||
public GemFireOperationsSessionRepository sessionRepository(@Qualifier("sessionRegionTemplate")
|
||||
GemfireOperations gemfireOperations) {
|
||||
|
||||
GemFireOperationsSessionRepository sessionRepository = new GemFireOperationsSessionRepository(gemfireOperations);
|
||||
|
||||
sessionRepository.setMaxInactiveIntervalInSeconds(getMaxInactiveIntervalInSeconds());
|
||||
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a Spring GemfireTemplate bean used to interact with GemFire's (Client)Cache {@link Region}
|
||||
* storing Sessions.
|
||||
*
|
||||
* @param gemFireCache reference to the single GemFire cache instance used by the {@link GemfireTemplate}
|
||||
* to perform GemFire cache data access operations.
|
||||
* @return a {@link GemfireTemplate} used to interact with GemFire's (Client)Cache {@link Region} storing Sessions.
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
*/
|
||||
@Bean
|
||||
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
||||
public GemfireTemplate sessionRegionTemplate(GemFireCache gemFireCache) {
|
||||
return new GemfireTemplate(gemFireCache.getRegion(getSpringSessionGemFireRegionName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a Spring GemFire {@link com.gemstone.gemfire.cache.Cache} {@link Region} bean used to store
|
||||
* and manage Sessions using either a client-server or peer-to-peer (p2p) topology.
|
||||
*
|
||||
* @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}.
|
||||
* @param sessionRegionAttributes the GemFire {@link RegionAttributes} used to configure the {@link Region}.
|
||||
* @return a {@link GemFireCacheTypeAwareRegionFactoryBean} used to configure and initialize a GemFire Cache
|
||||
* {@link Region} for storing and managing Sessions.
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
* @see #getClientRegionShortcut()
|
||||
* @see #getSpringSessionGemFireRegionName()
|
||||
* @see #getServerRegionShortcut()
|
||||
*/
|
||||
@Bean(name = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
||||
public GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> sessionRegion(GemFireCache gemfireCache,
|
||||
RegionAttributes<Object, ExpiringSession> sessionRegionAttributes) {
|
||||
|
||||
GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> serverRegion =
|
||||
new GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession>();
|
||||
|
||||
serverRegion.setGemfireCache(gemfireCache);
|
||||
serverRegion.setClientRegionShortcut(getClientRegionShortcut());
|
||||
serverRegion.setRegionAttributes(sessionRegionAttributes);
|
||||
serverRegion.setRegionName(getSpringSessionGemFireRegionName());
|
||||
serverRegion.setServerRegionShortcut(getServerRegionShortcut());
|
||||
|
||||
return serverRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a Spring GemFire {@link RegionAttributes} bean used to configure and initialize the GemFire cache
|
||||
* {@link Region} storing Sessions. Expiration is also configured for the {@link Region} on the basis that the
|
||||
* GemFire cache {@link Region} is a not a proxy, on either the client or server.
|
||||
*
|
||||
* @param gemfireCache a reference to the GemFire cache.
|
||||
* @return an instance of {@link RegionAttributes} used to configure and initialize the GemFire cache {@link Region}
|
||||
* for storing and managing Sessions.
|
||||
* @see org.springframework.data.gemfire.RegionAttributesFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.PartitionAttributes
|
||||
* @see #isExpirationAllowed(GemFireCache)
|
||||
*/
|
||||
@Bean
|
||||
@SuppressWarnings({ "unchecked", "deprecation" })
|
||||
public RegionAttributesFactoryBean sessionRegionAttributes(GemFireCache gemfireCache) {
|
||||
RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean();
|
||||
|
||||
regionAttributes.setKeyConstraint(SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT);
|
||||
regionAttributes.setValueConstraint(SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT);
|
||||
|
||||
if (isExpirationAllowed(gemfireCache)) {
|
||||
regionAttributes.setStatisticsEnabled(true);
|
||||
regionAttributes.setEntryIdleTimeout(new ExpirationAttributes(
|
||||
Math.max(getMaxInactiveIntervalInSeconds(), 0), ExpirationAction.INVALIDATE));
|
||||
}
|
||||
|
||||
return regionAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether expiration configuration is allowed to be set on the GemFire cache {@link Region}
|
||||
* used to store and manage Sessions.
|
||||
*
|
||||
* @param gemfireCache a reference to the GemFire cache.
|
||||
* @return a boolean indicating if a {@link Region} can be configured for Region entry idle-timeout expiration.
|
||||
* @see GemFireUtils#isClient(GemFireCache)
|
||||
* @see GemFireUtils#isProxy(ClientRegionShortcut)
|
||||
* @see GemFireUtils#isProxy(RegionShortcut)
|
||||
*/
|
||||
boolean isExpirationAllowed(GemFireCache gemfireCache) {
|
||||
return !(GemFireUtils.isClient(gemfireCache) ? GemFireUtils.isProxy(getClientRegionShortcut())
|
||||
: GemFireUtils.isProxy(getServerRegionShortcut()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a Spring GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions,
|
||||
* specifically on the 'principalName' property for quick lookup and queries. 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 the 'principalName' property 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 principalNameIndex(final GemFireCache gemfireCache) {
|
||||
IndexFactoryBean index = new IndexFactoryBean() {
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
if (GemFireUtils.isPeer(gemfireCache)) {
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
index.setCache(gemfireCache);
|
||||
index.setName("principalNameIdx");
|
||||
index.setExpression("principalName");
|
||||
index.setFrom(GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName()));
|
||||
index.setOverride(true);
|
||||
index.setType(IndexType.HASH);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.gemfire.GenericRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.client.Interest;
|
||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.InterestResultPolicy;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionAttributes;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* The GemFireCacheTypeAwareRegionFactoryBean class is a Spring {@link FactoryBean} used to construct, configure
|
||||
* and initialize the GemFire cache {@link Region} used to store and manage Session state.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.FactoryBean
|
||||
* @see org.springframework.beans.factory.InitializingBean
|
||||
* @see org.springframework.data.gemfire.GenericRegionFactoryBean
|
||||
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.InterestResultPolicy
|
||||
* @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
|
||||
*/
|
||||
public class GemFireCacheTypeAwareRegionFactoryBean<K, V> implements FactoryBean<Region<K, V>>, InitializingBean {
|
||||
|
||||
protected static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT =
|
||||
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT;
|
||||
|
||||
protected static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT =
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT;
|
||||
|
||||
protected static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME =
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
|
||||
|
||||
private ClientRegionShortcut clientRegionShortcut;
|
||||
|
||||
private GemFireCache gemfireCache;
|
||||
|
||||
private Region<K, V> region;
|
||||
|
||||
private RegionAttributes<K, V> regionAttributes;
|
||||
|
||||
private RegionShortcut serverRegionShortcut;
|
||||
|
||||
private String regionName;
|
||||
|
||||
/**
|
||||
* Post-construction initialization callback to create, configure and initialize the GemFire cache {@link Region}
|
||||
* used to store, replicate (distribute) and manage Session state. This method intelligently handles
|
||||
* both client-server and peer-to-peer (p2p) GemFire supported distributed system topologies.
|
||||
*
|
||||
* @throws Exception if the initialization of the GemFire cache {@link Region} fails.
|
||||
* @see org.springframework.session.data.gemfire.support.GemFireUtils#isClient(GemFireCache)
|
||||
* @see #getGemfireCache()
|
||||
* @see #newClientRegion(GemFireCache)
|
||||
* @see #newServerRegion(GemFireCache)
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
GemFireCache gemfireCache = getGemfireCache();
|
||||
|
||||
region = (GemFireUtils.isClient(gemfireCache) ? newClientRegion(gemfireCache)
|
||||
: newServerRegion(gemfireCache));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a GemFire cache {@link Region} using a peer-to-peer (p2p) GemFire topology to store
|
||||
* and manage Session state in a GemFire server cluster accessible from a GemFire cache client.
|
||||
*
|
||||
* @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}.
|
||||
* @return a peer-to-peer-based GemFire cache {@link Region} to store and manage Session state.
|
||||
* @throws Exception if the instantiation, configuration and initialization
|
||||
* of the GemFire cache {@link Region} fails.
|
||||
* @see org.springframework.data.gemfire.GenericRegionFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see #getRegionAttributes()
|
||||
* @see #getRegionName()
|
||||
* @see #getServerRegionShortcut()
|
||||
*/
|
||||
protected Region<K, V> newServerRegion(GemFireCache gemfireCache) throws Exception {
|
||||
GenericRegionFactoryBean<K, V> serverRegion = new GenericRegionFactoryBean<K, V>();
|
||||
|
||||
serverRegion.setCache(gemfireCache);
|
||||
serverRegion.setAttributes(getRegionAttributes());
|
||||
serverRegion.setRegionName(getRegionName());
|
||||
serverRegion.setShortcut(getServerRegionShortcut());
|
||||
serverRegion.afterPropertiesSet();
|
||||
|
||||
return serverRegion.getObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a GemFire cache {@link Region} using the client-server GemFire topology to store
|
||||
* and manage Session state in a GemFire server cluster accessible from a GemFire cache client.
|
||||
*
|
||||
* @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}.
|
||||
* @return a client-server-based GemFire cache {@link Region} to store and manage Session state.
|
||||
* @throws Exception if the instantiation, configuration and initialization
|
||||
* of the GemFire cache {@link Region} fails.
|
||||
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see #getClientRegionShortcut()
|
||||
* @see #getRegionAttributes()
|
||||
* @see #getRegionName()
|
||||
* @see #registerInterests(boolean)
|
||||
*/
|
||||
protected Region<K, V> newClientRegion(GemFireCache gemfireCache) throws Exception {
|
||||
ClientRegionFactoryBean<K, V> clientRegion = new ClientRegionFactoryBean<K, V>();
|
||||
|
||||
ClientRegionShortcut shortcut = getClientRegionShortcut();
|
||||
|
||||
clientRegion.setCache(gemfireCache);
|
||||
clientRegion.setAttributes(getRegionAttributes());
|
||||
clientRegion.setInterests(registerInterests(!GemFireUtils.isLocal(shortcut)));
|
||||
clientRegion.setRegionName(getRegionName());
|
||||
clientRegion.setShortcut(shortcut);
|
||||
clientRegion.afterPropertiesSet();
|
||||
|
||||
return clientRegion.getObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers interests in all keys when the client {@link Region} is non-local.
|
||||
*
|
||||
* @return an array of Interests specifying the server notifications of interests to the client.
|
||||
* @see org.springframework.data.gemfire.client.Interest
|
||||
*/
|
||||
/**
|
||||
* Decides whether interests will be registered for all keys. Interests is only registered on a client
|
||||
* and typically only when the client is a (CACHING) PROXY to the server (i.e. non-LOCAL only).
|
||||
*
|
||||
* @param register a boolean value indicating whether interests should be registered.
|
||||
* @return an array of Interests KEY/VALUE registrations.
|
||||
* @see org.springframework.data.gemfire.client.Interest
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Interest<K>[] registerInterests(boolean register) {
|
||||
return (!register ? new Interest[0] : new Interest[] {
|
||||
new Interest<String>("ALL_KEYS", InterestResultPolicy.KEYS)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the constructed GemFire cache {@link Region} used to store and manage Session state.
|
||||
*
|
||||
* @return the {@link Region} used to store and manage Session state.
|
||||
* @throws Exception if the {@link Region} reference cannot be obtained.
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
*/
|
||||
public Region<K, V> getObject() throws Exception {
|
||||
return region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specific type of GemFire cache {@link Region} this factory creates when initialized
|
||||
* or Region.class when uninitialized.
|
||||
*
|
||||
* @return the GemFire cache {@link Region} class type constructed by this factory.
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
public Class<?> getObjectType() {
|
||||
return (region != null ? region.getClass() : Region.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true indicating the GemFire cache {@link Region} created by this factory is the sole instance.
|
||||
*
|
||||
* @return true to indicate the GemFire cache {@link Region} storing and managing Sessions is a Singleton.
|
||||
*/
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Region} data policy used by the GemFire cache client to manage Session state.
|
||||
*
|
||||
* @param clientRegionShortcut a {@link ClientRegionShortcut} to specify the client {@link Region}
|
||||
* data management policy.
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
public void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) {
|
||||
this.clientRegionShortcut = clientRegionShortcut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Region} data policy used by the GemFire cache client to manage Session state. Defaults to
|
||||
* {@link ClientRegionShortcut#PROXY}.
|
||||
*
|
||||
* @return a {@link ClientRegionShortcut} specifying the client {@link Region} data management policy.
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_CLIENT_REGION_SHORTCUT
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
protected ClientRegionShortcut getClientRegionShortcut() {
|
||||
return (clientRegionShortcut != null ? clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reference to the GemFire cache used to construct the appropriate {@link Region}.
|
||||
*
|
||||
* @param gemfireCache a reference to the GemFire cache.
|
||||
* @throws IllegalArgumentException if the {@link GemFireCache} reference is null.
|
||||
*/
|
||||
public void setGemfireCache(GemFireCache gemfireCache) {
|
||||
Assert.notNull(gemfireCache, "The GemFireCache reference must not be null");
|
||||
this.gemfireCache = gemfireCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the GemFire cache used to construct the appropriate {@link Region}.
|
||||
*
|
||||
* @return a reference to the GemFire cache.
|
||||
* @throws IllegalStateException if the {@link GemFireCache} reference is null.
|
||||
*/
|
||||
protected GemFireCache getGemfireCache() {
|
||||
Assert.state(gemfireCache != null, "A reference to a GemFireCache was not properly configured");
|
||||
return gemfireCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region} used to
|
||||
* store and manage Session state.
|
||||
*
|
||||
* @param regionAttributes the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region}.
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
*/
|
||||
public void setRegionAttributes(RegionAttributes<K, V> regionAttributes) {
|
||||
this.regionAttributes = regionAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region} used to
|
||||
* store and manage Session state.
|
||||
*
|
||||
* @return the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region}.
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
*/
|
||||
protected RegionAttributes<K, V> getRegionAttributes() {
|
||||
return regionAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the GemFire cache {@link Region} use to store and manage Session state.
|
||||
*
|
||||
* @param regionName a String specifying the name of the GemFire cache {@link Region}.
|
||||
*/
|
||||
public void setRegionName(final String regionName) {
|
||||
this.regionName = regionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured name of the GemFire cache {@link Region} use to store and manage Session state.
|
||||
* Defaults to "ClusteredSpringSessions"
|
||||
*
|
||||
* @return a String specifying the name of the GemFire cache {@link Region}.
|
||||
* @see com.gemstone.gemfire.cache.Region#getName()
|
||||
*/
|
||||
protected String getRegionName() {
|
||||
return (StringUtils.hasText(regionName) ? regionName : DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Region} data policy used by the GemFire peer cache to manage Session state.
|
||||
*
|
||||
* @param serverRegionShortcut a {@link RegionShortcut} to specify the peer {@link Region} data management policy.
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
public void setServerRegionShortcut(RegionShortcut serverRegionShortcut) {
|
||||
this.serverRegionShortcut = serverRegionShortcut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Region} data policy used by the GemFire peer cache to manage Session state. Defaults to
|
||||
* {@link RegionShortcut#PARTITION}.
|
||||
*
|
||||
* @return a {@link RegionShortcut} specifying the peer {@link Region} data management policy.
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
protected RegionShortcut getServerRegionShortcut() {
|
||||
return (serverRegionShortcut != null ? serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;
|
||||
|
||||
/**
|
||||
* GemFireUtils is an abstract, extensible utility class for working with GemFire types and functionality
|
||||
* and is used by Spring Session's GemFire adapter support classes.
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public abstract class GemFireUtils {
|
||||
|
||||
/**
|
||||
* Null-safe method to close the given {@link Closeable} object.
|
||||
*
|
||||
* @param obj the {@link Closeable} object to close.
|
||||
* @return true if the {@link Closeable} object is not null and was successfully closed,
|
||||
* otherwise return false.
|
||||
* @see java.io.Closeable
|
||||
*/
|
||||
public static boolean close(Closeable obj) {
|
||||
if (obj != null) {
|
||||
try {
|
||||
obj.close();
|
||||
return true;
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the GemFire cache is a client.
|
||||
*
|
||||
* @param gemFireCache a reference to the GemFire cache.
|
||||
* @return a boolean value indicating whether the GemFire cache is a client.
|
||||
* @see com.gemstone.gemfire.cache.client.ClientCache
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
*/
|
||||
public static boolean isClient(GemFireCache gemFireCache) {
|
||||
boolean client = (gemFireCache instanceof ClientCache);
|
||||
client &= (!(gemFireCache instanceof GemFireCacheImpl) || ((GemFireCacheImpl) gemFireCache).isClient());
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the GemFire cache is a peer.
|
||||
*
|
||||
* @param gemFireCache a reference to the GemFire cache.
|
||||
* @return a boolean value indicating whether the GemFire cache is a peer.
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
*/
|
||||
public static boolean isPeer(GemFireCache gemFireCache) {
|
||||
return (gemFireCache instanceof Cache && !isClient(gemFireCache));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given {@link ClientRegionShortcut} is local only.
|
||||
*
|
||||
* @param shortcut the ClientRegionShortcut to evaluate.
|
||||
* @return a boolean value indicating if the {@link ClientRegionShortcut} is local or not.
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
public static boolean isLocal(ClientRegionShortcut shortcut) {
|
||||
switch (shortcut) {
|
||||
case LOCAL:
|
||||
case LOCAL_HEAP_LRU:
|
||||
case LOCAL_OVERFLOW:
|
||||
case LOCAL_PERSISTENT:
|
||||
case LOCAL_PERSISTENT_OVERFLOW:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the client {@link ClientRegionShortcut} is a proxy-based shortcut.
|
||||
* NOTE: "proxy"-based Regions keep no local state.
|
||||
*
|
||||
* @param shortcut the client {@link ClientRegionShortcut} to evaluate.
|
||||
* @return a boolean value indicating whether the client {@link ClientRegionShortcut} refers to
|
||||
* a proxy-based shortcut.
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
public static boolean isProxy(ClientRegionShortcut shortcut) {
|
||||
switch (shortcut) {
|
||||
case PROXY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the peer {@link RegionShortcut} is a proxy-based shortcut. NOTE: "proxy"-based Regions
|
||||
* keep no local state.
|
||||
*
|
||||
* @param shortcut the peer {@link RegionShortcut} to evaluate.
|
||||
* @return a boolean value indicating whether the peer {@link RegionShortcut} refers to a proxy-based shortcut.
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
public static boolean isProxy(RegionShortcut shortcut) {
|
||||
switch (shortcut) {
|
||||
case PARTITION_PROXY:
|
||||
case PARTITION_PROXY_REDUNDANT:
|
||||
case REPLICATE_PROXY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link Region} name to a {@link Region} path.
|
||||
*
|
||||
* @param regionName a String specifying the name of the {@link Region}.
|
||||
* @return a String path for the given {@link Region} by name.
|
||||
* @see com.gemstone.gemfire.cache.Region#getFullPath()
|
||||
* @see com.gemstone.gemfire.cache.Region#getName()
|
||||
*/
|
||||
public static String toRegionPath(String regionName) {
|
||||
return String.format("%1$s%2$s", Region.SEPARATOR, regionName);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user