diff --git a/gradle.properties b/gradle.properties index 36efa95..4a46bf7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,4 +13,4 @@ springIoVersion=Cairo-BUILD-SNAPSHOT springSecurityVersion=5.0.0.M3 springSessionVersion=2.0.0.BUILD-SNAPSHOT springShellVersion=1.2.0.RELEASE -version=2.0.0.BUILD-SNAPSHOT +version=2.0.0.ISSUE-2-BUILD-SNAPSHOT diff --git a/samples/boot/gemfire/src/main/java/sample/client/IntegrationTestConfiguration.java b/samples/boot/gemfire/src/main/java/sample/client/IntegrationTestConfiguration.java index 2158a15..65a2c3d 100644 --- a/samples/boot/gemfire/src/main/java/sample/client/IntegrationTestConfiguration.java +++ b/samples/boot/gemfire/src/main/java/sample/client/IntegrationTestConfiguration.java @@ -87,7 +87,7 @@ public class IntegrationTestConfiguration { private boolean isGemFireRegion(Object bean, String beanName) { - return (GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME.equals(beanName) + return (GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME.equals(beanName) || bean instanceof Region); } diff --git a/samples/boot/gemfire/src/main/java/sample/server/NativeGemFireServer.java b/samples/boot/gemfire/src/main/java/sample/server/NativeGemFireServer.java index 4caa128..e4bf1f5 100644 --- a/samples/boot/gemfire/src/main/java/sample/server/NativeGemFireServer.java +++ b/samples/boot/gemfire/src/main/java/sample/server/NativeGemFireServer.java @@ -157,7 +157,7 @@ public final class NativeGemFireServer implements Runnable { regionFactory.setEntryIdleTimeout(newExpirationAttributes(1800, ExpirationAction.INVALIDATE)); Region region = regionFactory.create( - GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME); return gemfireCache; } diff --git a/samples/javaconfig/gemfire-clientserver/src/main/java/sample/IntegrationTestConfig.java b/samples/javaconfig/gemfire-clientserver/src/main/java/sample/IntegrationTestConfig.java index e8574fa..7278360 100644 --- a/samples/javaconfig/gemfire-clientserver/src/main/java/sample/IntegrationTestConfig.java +++ b/samples/javaconfig/gemfire-clientserver/src/main/java/sample/IntegrationTestConfig.java @@ -87,7 +87,7 @@ public abstract class IntegrationTestConfig { private boolean isGemFireRegion(Object bean, String beanName) { - return (GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME.equals(beanName) + return (GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME.equals(beanName) || bean instanceof Region); } diff --git a/samples/xml/gemfire-clientserver/src/main/java/sample/ClientServerReadyBeanPostProcessor.java b/samples/xml/gemfire-clientserver/src/main/java/sample/ClientServerReadyBeanPostProcessor.java index f68325d..a9cef95 100644 --- a/samples/xml/gemfire-clientserver/src/main/java/sample/ClientServerReadyBeanPostProcessor.java +++ b/samples/xml/gemfire-clientserver/src/main/java/sample/ClientServerReadyBeanPostProcessor.java @@ -88,7 +88,7 @@ public class ClientServerReadyBeanPostProcessor implements BeanPostProcessor { private boolean isGemFireRegion(Object bean, String beanName) { - return (GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME.equals(beanName) + return (GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME.equals(beanName) || bean instanceof Region); } diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java index 1ffe6ac..61c0e9f 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java @@ -885,6 +885,16 @@ public abstract class AbstractGemFireOperationsSessionRepository extends CacheLi this.lock = (lock != null ? lock : this); } + /* (non-Javadoc) */ + public static GemFireSessionAttributes create() { + return new GemFireSessionAttributes(); + } + + /* (non-Javadoc) */ + public static GemFireSessionAttributes create(Object lock) { + return new GemFireSessionAttributes(lock); + } + /* (non-Javadoc) */ public void setAttribute(String attributeName, Object attributeValue) { diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java index 7d06972..1c0b66d 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java @@ -24,102 +24,63 @@ import java.lang.annotation.Target; import javax.servlet.http.HttpSession; +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.Pool; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.session.Session; +import org.springframework.session.data.gemfire.serialization.SessionSerializer; import org.springframework.session.web.http.SessionRepositoryFilter; /** - * Add this annotation to a Spring {@code @Configuration} class to expose the {@link SessionRepositoryFilter} - * as a bean named {@literal springSessionRepositoryFilter} back the {@link HttpSession} - * by Pivotal GemFire or Apache Geode. + * Add this annotation to a Spring application defined {@code @Configuration} class exposing + * the {@link SessionRepositoryFilter} as a bean named {@literal springSessionRepositoryFilter} + * to back the {@link HttpSession} by Apache Geode or Pivotal GemFire. * - * In order to use this annotation, a single Pivotal GemFire/Apache Geode {@link org.apache.geode.cache.Cache} - * or {@link org.apache.geode.cache.client.ClientCache} instance must be provided. + * In order to use this annotation, a single Apache Geode / Pivotal GemFire {@link Cache} or {@link ClientCache} + * instance must be provided. * * For example: * *
  * 
  * {@literal @Configuration}
+ * {@literal @PeerCacheApplication}
  * {@literal @EnableGemFireHttpSession}
- * public class GemFirePeerCacheHttpSessionConfiguration {
+ * public class PeerCacheHttpSessionConfiguration {
  *
- *     {@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 cache  = new CacheFactoryBean();
- *
- *       cache.setClose(true);
- *       cache.setProperties(gemfireProperties());
- *
- *       return cache;
- *     }
  * }
  *  
* - * Alternatively, Spring Session can be configured to use Pivotal GemFire/Apache Geode as a cache client - * with a dedicated Pivotal GemFire/Apache Geode cluster and a {@link org.apache.geode.cache.client.ClientCache}. + * Alternatively, Spring Session can be configured to use Apache Geode / Pivotal GemFire as a cache client + * with a dedicated Apache Geode / Pivotal GemFire cluster. * * For example: * * * {@literal @Configuration} + * {@literal @ClientCacheApplication} * {@literal @EnableGemFireHttpSession} - * public class GemFireClientCacheHttpSessionConfiguration { + * public class ClientCacheHttpSessionConfiguration { * - * {@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 clientCache = new ClientCacheFactoryBean(); - * - * clientCache.setClose(true) - * clientCache.setProperties(gemfireProperties()); - * - * return clientCache; - * } - * - * {@literal @Bean} - * public PoolFactoryBean gemfirePool() { - * - * PoolFactoryBean pool = new PoolFactoryBean(); - * - * pool.addServer(new ConnectionEndpoint("localhost", 40404); - * - * return pool; - * } * } * * * More advanced configurations can extend {@link GemFireHttpSessionConfiguration} instead. * * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.client.Pool + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.Import + * @see org.springframework.session.Session * @see org.springframework.session.config.annotation.web.http.EnableSpringHttpSession * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration * @since 1.1.0 @@ -132,59 +93,74 @@ import org.springframework.session.web.http.SessionRepositoryFilter; public @interface EnableGemFireHttpSession { /** - * Defines the GemFire ClientCache Region DataPolicy. + * Defines the {@link ClientCache} {@link Region} data management policy. * - * @return a ClientRegionShortcut used to specify and configure the ClientCache Region - * DataPolicy. + * @return a {@link ClientRegionShortcut} used to configure the {@link ClientCache} {@link Region} + * data management policy. * @see org.apache.geode.cache.client.ClientRegionShortcut */ 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. + * Identifies the {@link Session} attributes by name that will be indexed for query operations. * - * @return an array of Strings identifying the names of Session attributes to index. + * For instance, find all {@link Session Sessions} in GemFire or Geode having attribute A defined with value X. + * + * @return an array of {@link String Strings} identifying the names of {@link Session} attributes to index. */ String[] indexableSessionAttributes() default {}; /** - * Defines the maximum interval in seconds that a {@link Session} can remain inactive - * before it is considered expired. Defaults to 1800 seconds, or 30 minutes. + * Defines the maximum interval in seconds that a {@link Session} can remain inactive before it expires. * - * @return an integer value defining the maximum inactive interval in seconds - * declaring the {@link Session} expired. + * Defaults to 1800 seconds, or 30 minutes. + * + * @return an integer value defining the maximum inactive interval in seconds before the {@link Session} expires. */ int maxInactiveIntervalInSeconds() default 1800; /** - * Specifies the name of the specific GemFire {@link org.apache.geode.cache.client.Pool} used - * by the Spring Session Data GemFire client Region ('ClusteredSpringSessions') when performing - * cache operations. This is attribute is only used in the client/server topology. + * Specifies the name of the specific {@link Pool} used by the client cache {@link Region} + * (i.e. {@literal ClusteredSpringSessions}) when performing cache data access operations. * - * @return the name of the GemFire {@link org.apache.geode.cache.client.Pool} to be used - * by the client Region used to manage (HTTP) Sessions. - * @see org.springframework.data.gemfire.config.xml.GemfireConstants#DEFAULT_GEMFIRE_POOL_NAME + * This is attribute is only used in the client/server topology. + * + * @return the name of the {@link Pool} to be used by the client cache Region to send {@link Session} state + * to the cluster of servers. + * @see GemFireHttpSessionConfiguration#DEFAULT_POOL_NAME */ - String poolName() default GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME; + String poolName() default GemFireHttpSessionConfiguration.DEFAULT_POOL_NAME; /** - * Defines the name of the GemFire (Client)Cache Region used to store Sessions. + * Defines the name of the (client)cache {@link Region} used to store {@link Session} state. * - * @return a String specifying the name of the GemFire (Client)Cache Region used to - * store Sessions. - * @see org.apache.geode.cache.Region#getName() + * @return a {@link String} specifying the name of the (client)cace {@link Region} + * used to store {@link Session} state. + * @see GemFireHttpSessionConfiguration#DEFAULT_SESSION_REGION_NAME */ - String regionName() default "ClusteredSpringSessions"; + String regionName() default GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME; /** - * Defines the GemFire, Peer Cache Region DataPolicy. + * Defines the {@link Cache} {@link Region} data management policy. * - * @return a RegionShortcut used to specify and configure the Peer Cache Region - * DataPolicy. + * @return a {@link RegionShortcut} used to specify and configure the {@link Cache} {@link Region} + * data management policy. * @see org.apache.geode.cache.RegionShortcut */ RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION; + /** + * Defines the bean name of the {@link SessionSerializer} used to serialize {@link Session} state + * between client and server or to disk when persisting or overflowing {@link Session} state. + * + * The bean referred to by its name must be of type {@link SessionSerializer}. + * + * Defaults to {@literal SessionDataSerializer}. + * + * @return a {@link String} containing the bean name of the configured {@link SessionSerializer}. + * @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer + * @see org.springframework.session.data.gemfire.serialization.SessionSerializer + */ + String sessionSerializerBeanName() default GemFireHttpSessionConfiguration.DEFAULT_SESSION_SERIALIZER_BEAN_NAME; + } diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java index a242fb8..e2afa00 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java @@ -19,12 +19,14 @@ package org.springframework.session.data.gemfire.config.annotation.web.http; import java.util.Optional; import java.util.concurrent.TimeUnit; +import org.apache.geode.cache.Cache; import org.apache.geode.cache.ExpirationAction; import org.apache.geode.cache.ExpirationAttributes; import org.apache.geode.cache.GemFireCache; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionAttributes; import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.client.ClientRegionShortcut; import org.apache.geode.cache.client.Pool; @@ -33,6 +35,7 @@ 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.Import; import org.springframework.context.annotation.ImportAware; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; @@ -43,11 +46,15 @@ import org.springframework.data.gemfire.IndexType; import org.springframework.data.gemfire.RegionAttributesFactoryBean; import org.springframework.data.gemfire.config.xml.GemfireConstants; import org.springframework.session.Session; +import org.springframework.session.SessionRepository; 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.config.annotation.web.http.support.SessionAttributesIndexFactoryBean; +import org.springframework.session.data.gemfire.serialization.SessionSerializer; +import org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer; +import org.springframework.session.data.gemfire.serialization.pdx.provider.PdxSerializableSessionSerializer; import org.springframework.session.data.gemfire.support.GemFireUtils; import org.springframework.util.StringUtils; @@ -89,38 +96,51 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat implements BeanClassLoaderAware, ImportAware { /** - * The default maximum interval in seconds in which a Session can remain inactive - * before it is considered expired. + * Default maximum interval in seconds in which a {@link Session} can remain inactive before it expires. */ public static final int DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS = (int) TimeUnit.MINUTES.toSeconds(30); - protected static final Class SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT = Object.class; - protected static final Class SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT = GemFireSession.class; + /** + * Key and Value class type constraints applied to the {@link Session} {@link Region}. + */ + protected static final Class SESSION_REGION_KEY_CONSTRAINT = Object.class; + protected static final Class SESSION_REGION_VALUE_CONSTRAINT = GemFireSession.class; /** - * The default {@link ClientRegionShortcut} used to configure the GemFire ClientCache - * Region that will store Spring Sessions. + * Default {@link ClientRegionShortcut} used to configure the data management policy of the {@link ClientCache} + * {@link Region} that will store {@link Session} state. */ public static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY; /** - * The default {@link RegionShortcut} used to configure the GemFire Cache Region that - * will store Spring Sessions. + * Default {@link RegionShortcut} used to configure the data management policy of the {@link Cache} {@link Region} + * that will store {@link Session} state. */ public static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionShortcut.PARTITION; /** - * Name of the GemFire {@link Pool} used by the client Region for managing Session state information. + * Name of the connection {@link Pool} used by the client {@link Region} to send {@link Session} state + * to the cluster of Geode servers. */ - public static final String DEFAULT_GEMFIRE_POOL_NAME = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME; + public static final String DEFAULT_POOL_NAME = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME; /** - * The default name of the Gemfire (Client)Cache Region used to store Sessions. + * Default name of (Client)Cache {@link Region} used to store {@link Session} state. */ - public static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = "ClusteredSpringSessions"; + public static final String DEFAULT_SESSION_REGION_NAME = "ClusteredSpringSessions"; /** - * The default names of all Session attributes that should be indexed by GemFire. + * Set of defaults for {@link Session} serialization. + */ + public static final String SESSION_DATA_SERIALIZER_BEAN_NAME = "SessionDataSerializer"; + public static final String SESSION_PDX_SERIALIZER_BEAN_NAME = "SessionPdxSerializer"; + public static final String SESSION_SERIALIZER_QUALIFIER_PROPERTY_NAME = + "spring.session.data.geode.serializer.qualifier"; + + public static final String DEFAULT_SESSION_SERIALIZER_BEAN_NAME = SESSION_DATA_SERIALIZER_BEAN_NAME; + + /** + * Defaults names of all {@link Session} attributes that will be indexed by Apache Geode. */ public static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = {}; @@ -132,18 +152,18 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat private RegionShortcut serverRegionShortcut = DEFAULT_SERVER_REGION_SHORTCUT; - private String poolName = DEFAULT_GEMFIRE_POOL_NAME; + private String poolName = DEFAULT_POOL_NAME; - private String springSessionGemFireRegionName = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME; + private String sessionRegionName = DEFAULT_SESSION_REGION_NAME; + + private String sessionSerializerBeanName = DEFAULT_SESSION_SERIALIZER_BEAN_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. + * Sets a reference to the {@link ClassLoader} used by the Spring container to load bean class types. * - * @param beanClassLoader the ClassLoader used by the Spring container to load bean - * class types. + * @param beanClassLoader {@link ClassLoader} used by the Spring container to load bean class types. * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader) * @see java.lang.ClassLoader */ @@ -152,10 +172,9 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat } /** - * Gets a reference to the {@link ClassLoader} used to load bean definition class - * types in a Spring context. + * Returns a reference to the {@link ClassLoader} used by the Spring container to load bean class types. * - * @return the ClassLoader used by the Spring container to load bean class types. + * @return the {@link ClassLoader} used by the Spring container to load bean class types. * @see java.lang.ClassLoader */ protected ClassLoader getBeanClassLoader() { @@ -163,11 +182,14 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat } /** - * Sets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache - * Region that will store Spring Sessions. + * Gets the {@link ClientRegionShortcut} used to configure the data management policy of the {@link ClientCache} + * {@link Region} that will store {@link Session} state. * - * @param shortcut the ClientRegionShortcut used to configure the GemFire ClientCache - * Region. + * Defaults to {@link ClientRegionShortcut#PROXY}. + * + * @param shortcut {@link ClientRegionShortcut} used to configure the data management policy + * of the {@link ClientCache} {@link Region}. + * @see EnableGemFireHttpSession#clientRegionShortcut() * @see org.apache.geode.cache.client.ClientRegionShortcut */ public void setClientRegionShortcut(ClientRegionShortcut shortcut) { @@ -175,91 +197,92 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat } /** - * Gets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache - * Region that will store Spring Sessions. Defaults to - * {@link ClientRegionShortcut#PROXY}. + * Gets the {@link ClientRegionShortcut} used to configure the data management policy of the {@link ClientCache} + * {@link Region} that will store {@link Session} state. * - * @return the ClientRegionShortcut used to configure the GemFire ClientCache Region. + * Defaults to {@link ClientRegionShortcut#PROXY}. + * + * @return the {@link ClientRegionShortcut} used to configure the data management policy + * of the {@link ClientCache} {@link Region}. * @see org.apache.geode.cache.client.ClientRegionShortcut - * @see EnableGemFireHttpSession#clientRegionShortcut() */ protected ClientRegionShortcut getClientRegionShortcut() { return Optional.ofNullable(this.clientRegionShortcut).orElse(DEFAULT_CLIENT_REGION_SHORTCUT); } /** - * Sets the names of all Session attributes that should be indexed by GemFire. + * Sets the names of all {@link Session} attributes that will be indexed. * - * @param indexableSessionAttributes an array of Strings indicating the names of all - * Session attributes for which an Index will be created by GemFire. + * @param indexableSessionAttributes an array of {@link String Strings} containing the names + * of all {@link Session} attributes for which an Index will be created. + * @see EnableGemFireHttpSession#indexableSessionAttributes() */ public void setIndexableSessionAttributes(String[] indexableSessionAttributes) { this.indexableSessionAttributes = indexableSessionAttributes; } /** - * Get the names of all Session attributes that should be indexed by GemFire. + * Get the names of all {@link Session} attributes that will be indexed. * - * @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() + * @return an array of {@link String Strings} containing the names of all {@link Session} attributes + * for which an Index will be created. Defaults to an empty array if unspecified. */ protected String[] getIndexableSessionAttributes() { return Optional.ofNullable(this.indexableSessionAttributes).orElse(DEFAULT_INDEXABLE_SESSION_ATTRIBUTES); } /** - * Sets the maximum interval in seconds in which a Session can remain inactive before - * it is considered expired. + * Sets the maximum interval in seconds in which a {@link Session} can remain inactive before it expires. * - * @param maxInactiveIntervalInSeconds an integer value specifying the maximum - * interval in seconds that a Session can remain inactive before it is considered - * expired. + * @param maxInactiveIntervalInSeconds integer value specifying the maximum interval in seconds + * that a {@link Session} can remain inactive before it expires. + * @see EnableGemFireHttpSession#maxInactiveIntervalInSeconds() */ public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) { this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds; } /** - * Gets the maximum interval in seconds in which a {@link Session} can remain inactive - * before it is considered expired. + * Gets the maximum interval in seconds in which a {@link Session} can remain inactive before it expires. * * @return an integer value specifying the maximum interval in seconds that a {@link Session} can remain inactive - * before it is considered expired. - * @see EnableGemFireHttpSession#maxInactiveIntervalInSeconds() + * before it expires. */ protected int getMaxInactiveIntervalInSeconds() { return this.maxInactiveIntervalInSeconds; } /** - * Sets the name of the GemFire {@link Pool} used by the client Region for managing Sessions - * during cache operations involving the server. + * Sets the name of the {@link Pool} used by the client {@link Region} to send {@link Session} + * to the cluster of servers during cache operations. * - * @param poolName the name of a GemFire {@link Pool}. - * @see Pool#getName() + * @param poolName {@link String} containing the name of a {@link Pool}. + * @see EnableGemFireHttpSession#poolName() */ public void setPoolName(String poolName) { this.poolName = poolName; } /** - * Returns the name of the GemFire {@link Pool} used by the client Region for managing Sessions - * during cache operations involving the server. + * Returns the name of the {@link Pool} used by the client {@link Region} to send {@link Session} + * to the cluster of servers during cache operations. * - * @return the name of a GemFire {@link Pool}. - * @see Pool#getName() + * @return a {@link String} containing the name of a {@link Pool}. + * @see org.apache.geode.cache.client.Pool#getName() */ protected String getPoolName() { - return Optional.ofNullable(this.poolName).filter(StringUtils::hasText).orElse(DEFAULT_GEMFIRE_POOL_NAME); + return Optional.ofNullable(this.poolName).filter(StringUtils::hasText).orElse(DEFAULT_POOL_NAME); } /** - * Sets the {@link RegionShortcut} used to configure the GemFire Cache Region that - * will store Spring Sessions. + * Sets the {@link RegionShortcut} used to configure the data management policy of the {@link Cache} {@link Region} + * that will store {@link Session} state. * - * @param shortcut the RegionShortcut used to configure the GemFire Cache Region. + * Defaults to {@link RegionShortcut#PARTITION}. + * + * @param shortcut {@link RegionShortcut} used to configure the data management policy + * of the {@link Cache} {@link Region}. + * @see EnableGemFireHttpSession#serverRegionShortcut() * @see org.apache.geode.cache.RegionShortcut */ public void setServerRegionShortcut(RegionShortcut shortcut) { @@ -267,47 +290,85 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat } /** - * Gets the {@link RegionShortcut} used to configure the GemFire Cache Region that - * will store Spring Sessions. Defaults to {@link RegionShortcut#PARTITION}. + * Gets the {@link RegionShortcut} used to configure the data management policy of the {@link Cache} {@link Region} + * that will store {@link Session} state. * - * @return the RegionShortcut used to configure the GemFire Cache Region. + * Defaults to {@link RegionShortcut#PARTITION}. + * + * @return the {@link RegionShortcut} used to configure the data management policy + * of the {@link Cache} {@link Region}. * @see org.apache.geode.cache.RegionShortcut - * @see EnableGemFireHttpSession#serverRegionShortcut() */ protected RegionShortcut getServerRegionShortcut() { return Optional.ofNullable(this.serverRegionShortcut).orElse(DEFAULT_SERVER_REGION_SHORTCUT); } /** - * Sets the name of the Gemfire (Client)Cache Region used to store Sessions. + * Sets the name of the (Client)Cache {@link Region} used to store {@link Session} state. * - * @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 org.apache.geode.cache.Region#getName() + * @param sessionRegionName {@link String} specifying the name of the (Client)Cache {@link Region} + * used to store {@link Session} state. * @see EnableGemFireHttpSession#regionName() */ - protected String getSpringSessionGemFireRegionName() { - return Optional.ofNullable(this.springSessionGemFireRegionName).filter(StringUtils::hasText) - .orElse(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + public void setSessionRegionName(String sessionRegionName) { + this.sessionRegionName = sessionRegionName; } /** - * Callback with the {@link AnnotationMetadata} of the class containing @Import - * annotation that imported this @Configuration class. + * Returns the name of the (Client)Cache {@link Region} used to store {@link Session} state. * - * @param importMetadata the AnnotationMetadata of the class importing - * this @Configuration class. + * @return a {@link String} specifying the name of the (Client)Cache {@link Region} + * used to store {@link Session} state. + * @see org.apache.geode.cache.Region#getName() + */ + protected String getSessionRegionName() { + return Optional.ofNullable(this.sessionRegionName).filter(StringUtils::hasText) + .orElse(DEFAULT_SESSION_REGION_NAME); + } + + /** + * Sets the {@link String bean name} of the Spring bean declared in the Spring application context + * defining the serialization strategy for serializing the {@link Session}. + * + * The serialization strategy and bean referred to by its name must be an implementation of + * {@link SessionSerializer}. + * + * Defaults to {@literal SessionDataSerializer}. + * + * @param sessionSerializerBeanName {@link String bean name} of the {@link SessionSerializer} used to + * serialize the {@link Session}. + * @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer + * @see org.springframework.session.data.gemfire.serialization.SessionSerializer + */ + public void setSessionSerializerBeanName(String sessionSerializerBeanName) { + this.sessionSerializerBeanName = sessionSerializerBeanName; + } + + /** + * Returns the configured {@link String bean name} of the Spring bean declared in the Spring application context + * defining the serialization strategy for serializing the {@link Session}. + * + * The serialization strategy and bean referred to by its name must be an implementation of + * {@link SessionSerializer}. + * + * Defaults to {@literal SessionDataSerializer}. + * + * @return the {@link String bean name} of the {@link SessionSerializer} used to serialize the {@link Session}. + * @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer + * @see org.springframework.session.data.gemfire.serialization.SessionSerializer + */ + protected String getSessionSerializerBeanName() { + return Optional.ofNullable(this.sessionSerializerBeanName).filter(StringUtils::hasText) + .orElse(DEFAULT_SESSION_SERIALIZER_BEAN_NAME); + } + + /** + * Callback with the {@link AnnotationMetadata} of the class containing {@link Import @Import} annotation + * that imported this {@link Configuration @Configuration} class. + * + * @param importMetadata {@link AnnotationMetadata} of the application class importing + * this {@link Configuration} class. + * @see org.springframework.core.type.AnnotationMetadata */ public void setImportMetadata(AnnotationMetadata importMetadata) { @@ -329,17 +390,24 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat setServerRegionShortcut(RegionShortcut.class.cast( enableGemFireHttpSessionAttributes.getEnum("serverRegionShortcut"))); - setSpringSessionGemFireRegionName(enableGemFireHttpSessionAttributes.getString("regionName")); + setSessionRegionName(enableGemFireHttpSessionAttributes.getString("regionName")); + + setSessionSerializerBeanName( + enableGemFireHttpSessionAttributes.getString("sessionSerializerBeanName")); + + System.setProperty(SESSION_SERIALIZER_QUALIFIER_PROPERTY_NAME, getSessionSerializerBeanName()); } /** - * Defines the Spring SessionRepository bean used to interact with GemFire as a Spring - * Session provider. + * Defines the {@link SessionRepository} bean used to interact with Apache Geode or Pivotal GemFire + * as the 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. + * @param gemfireOperations instance of {@link GemfireOperations} used to manage {@link Session} state + * in Apache Geode or Pivotal GemFire. + * @return a {@link GemFireOperationsSessionRepository} for managing (clustering/replicating) {@link Session} state + * in Apache Geode or Pivotal GemFire. + * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository + * @see org.springframework.data.gemfire.GemfireOperations */ @Bean public GemFireOperationsSessionRepository sessionRepository( @@ -354,38 +422,21 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat } /** - * Defines a Spring GemfireTemplate bean used to interact with GemFire's (Client)Cache - * {@link Region} storing Sessions. + * Defines the {@link Region} used to store and manage {@link Session} state in either a client-server + * or peer-to-peer (p2p) topology. * - * @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 org.apache.geode.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 org.apache.geode.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 org.apache.geode.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. + * @param gemfireCache reference to the {@link GemFireCache}. + * @param sessionRegionAttributes {@link RegionAttributes} used to configure the {@link Region}. + * @return a {@link GemFireCacheTypeAwareRegionFactoryBean} used to configure and initialize + * the cache {@link Region} used to store and manage {@link Session} state. + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.RegionAttributes * @see #getClientRegionShortcut() - * @see #getSpringSessionGemFireRegionName() + * @see #getPoolName() * @see #getServerRegionShortcut() + * @see #getSessionRegionName() */ - @Bean(name = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME) + @Bean(name = DEFAULT_SESSION_REGION_NAME) public GemFireCacheTypeAwareRegionFactoryBean sessionRegion(GemFireCache gemfireCache, @Qualifier("sessionRegionAttributes") RegionAttributes sessionRegionAttributes) { @@ -396,21 +447,22 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat sessionRegion.setGemfireCache(gemfireCache); sessionRegion.setPoolName(getPoolName()); sessionRegion.setRegionAttributes(sessionRegionAttributes); - sessionRegion.setRegionName(getSpringSessionGemFireRegionName()); + sessionRegion.setRegionName(getSessionRegionName()); sessionRegion.setServerRegionShortcut(getServerRegionShortcut()); return sessionRegion; } /** - * 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. + * Defines a {@link RegionAttributes} used to configure and initialize the cache {@link Region} + * used to store {@link Session} state. * - * @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. + * Expiration is also configured for the {@link Region} on the basis that the cache {@link Region} + * is a not a proxy on either the client or server. + * + * @param gemfireCache reference to the {@link GemFireCache}. + * @return an instance of {@link RegionAttributes} used to configure and initialize cache {@link Region} + * used to store and manage {@link Session} state. * @see org.springframework.data.gemfire.RegionAttributesFactoryBean * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.PartitionAttributes @@ -422,8 +474,8 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean(); - regionAttributes.setKeyConstraint(SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT); - regionAttributes.setValueConstraint(SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT); + regionAttributes.setKeyConstraint(SESSION_REGION_KEY_CONSTRAINT); + regionAttributes.setValueConstraint(SESSION_REGION_VALUE_CONSTRAINT); if (isExpirationAllowed(gemfireCache)) { regionAttributes.setStatisticsEnabled(true); @@ -435,21 +487,52 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat } /** - * Determines whether expiration configuration is allowed to be set on the GemFire - * cache {@link Region} used to store and manage Sessions. + * Determines whether expiration configuration is allowed to be set on the cache {@link Region} + * used to store and manage {@link Session} state. * - * @param gemfireCache a reference to the GemFire cache. - * @return a boolean indicating if a {@link Region} can be configured for Region entry + * @param gemfireCache reference to the {@link GemFireCache}. + * @return a boolean indicating if a {@link Region} can be configured for {@link 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()) + + return !(GemFireUtils.isClient(gemfireCache) + ? GemFireUtils.isProxy(getClientRegionShortcut()) : GemFireUtils.isProxy(getServerRegionShortcut())); } + /** + * Defines a {@link GemfireTemplate} bean used to interact with the (Client)Cache {@link Region} + * used to store {@link Session} state. + * + * @param gemfireCache reference to the single {@link GemFireCache} instance used by the {@link GemfireTemplate} + * to perform cache {@link Region} data access operations. + * @return a {@link GemfireTemplate} used to interact with the (Client)Cache {@link Region} + * used to store {@link Session} state. + * @see org.springframework.data.gemfire.GemfireTemplate + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see #getSessionRegionName() + */ + @Bean + @DependsOn(DEFAULT_SESSION_REGION_NAME) + public GemfireTemplate sessionRegionTemplate(GemFireCache gemfireCache) { + return new GemfireTemplate(gemfireCache.getRegion(getSessionRegionName())); + } + + @Bean(SESSION_DATA_SERIALIZER_BEAN_NAME) + public Object sessionDataSerializer() { + return new PdxSerializableSessionSerializer(); + } + + @Bean(SESSION_PDX_SERIALIZER_BEAN_NAME) + public Object sessionPdxSerializer() { + return new DataSerializableSessionSerializer(); + } + /** * Defines a GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions, * specifically on the 'principalName' property for quick lookup of Sessions by 'principalName'. @@ -461,19 +544,19 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat * @see org.apache.geode.cache.GemFireCache */ @Bean - @DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME) + @DependsOn(DEFAULT_SESSION_REGION_NAME) public IndexFactoryBean principalNameIndex(GemFireCache gemfireCache) { - IndexFactoryBean index = new IndexFactoryBean(); + IndexFactoryBean principalNameIndex = new IndexFactoryBean(); - index.setCache(gemfireCache); - index.setName("principalNameIndex"); - index.setExpression("principalName"); - index.setFrom(GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName())); - index.setOverride(true); - index.setType(IndexType.HASH); + principalNameIndex.setCache(gemfireCache); + principalNameIndex.setName("principalNameIndex"); + principalNameIndex.setExpression("principalName"); + principalNameIndex.setFrom(GemFireUtils.toRegionPath(getSessionRegionName())); + principalNameIndex.setOverride(true); + principalNameIndex.setType(IndexType.HASH); - return index; + return principalNameIndex; } /** @@ -488,14 +571,14 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat * @see org.apache.geode.cache.GemFireCache */ @Bean - @DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME) + @DependsOn(DEFAULT_SESSION_REGION_NAME) public SessionAttributesIndexFactoryBean sessionAttributesIndex(GemFireCache gemfireCache) { SessionAttributesIndexFactoryBean sessionAttributesIndex = new SessionAttributesIndexFactoryBean(); sessionAttributesIndex.setGemFireCache(gemfireCache); sessionAttributesIndex.setIndexableSessionAttributes(getIndexableSessionAttributes()); - sessionAttributesIndex.setRegionName(getSpringSessionGemFireRegionName()); + sessionAttributesIndex.setRegionName(getSessionRegionName()); return sessionAttributesIndex; } diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBean.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBean.java index e9bd72b..cde75ed 100644 --- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBean.java +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBean.java @@ -70,11 +70,11 @@ public class GemFireCacheTypeAwareRegionFactoryBean protected static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT; - protected static final String DEFAULT_GEMFIRE_POOL_NAME = - GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME; + protected static final String DEFAULT_POOL_NAME = + GemFireHttpSessionConfiguration.DEFAULT_POOL_NAME; - protected static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = - GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME; + protected static final String DEFAULT_SESSION_REGION_NAME = + GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME; private BeanFactory beanFactory; @@ -325,7 +325,7 @@ public class GemFireCacheTypeAwareRegionFactoryBean * @see Pool#getName() */ protected String getPoolName() { - return Optional.ofNullable(this.poolName).filter(StringUtils::hasText).orElse(DEFAULT_GEMFIRE_POOL_NAME); + return Optional.ofNullable(this.poolName).filter(StringUtils::hasText).orElse(DEFAULT_POOL_NAME); } /** @@ -370,8 +370,7 @@ public class GemFireCacheTypeAwareRegionFactoryBean * @see org.apache.geode.cache.Region#getName() */ protected String getRegionName() { - return Optional.ofNullable(this.regionName).filter(StringUtils::hasText) - .orElse(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + return Optional.ofNullable(this.regionName).filter(StringUtils::hasText).orElse(DEFAULT_SESSION_REGION_NAME); } /** diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/SerializationException.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/SerializationException.java new file mode 100644 index 0000000..5b2458f --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/SerializationException.java @@ -0,0 +1,76 @@ +/* + * Copyright 2017 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.serialization; + +/** + * The SerializationException class is a {@link RuntimeException} indicating an error occurred while attempting to + * serialize a {@link org.springframework.session.Session}. + * + * @author John Blum + * @see java.lang.RuntimeException + * @since 2.0.0 + */ +@SuppressWarnings("unused") +public class SerializationException extends RuntimeException { + + /** + * Constructs a default instance of {@link SerializationException} with no {@link String message} + * or {@link Throwable cause}. + * + * @see RuntimeException + */ + public SerializationException() { + } + + /** + * Constructs a new instance of {@link SerializationException} initialized with the given {@link String message} + * describing the serialization error. + * + * @param message {@link String} describing the serialization error. + * @see RuntimeException(String) + * @see java.lang.String + */ + public SerializationException(String message) { + super(message); + } + + /** + * Constructs a new instance of {@link SerializationException} initialized with the given {@link Throwable cause} + * of the serialization error. + * + * @param cause {@link Throwable underlying cause} of the serialization error. + * @see RuntimeException(Throwable) + * @see java.lang.Throwable + */ + public SerializationException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new instance of {@link SerializationException} initialized with the given {@link String message} + * describing the serialization error and {@link Throwable cause} of the serialization error. + * + * @param message {@link String} describing the serialization error. + * @param cause {@link Throwable underlying cause} of the serialization error. + * @see RuntimeException(String, Throwable) + * @see java.lang.Throwable + * @see java.lang.String + */ + public SerializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/SessionSerializer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/SessionSerializer.java new file mode 100644 index 0000000..6a106f3 --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/SessionSerializer.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 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.serialization; + +import java.util.Optional; + +/** + * The {@link SessionSerializer} interface is a Service Provider Interface (SPI) for providers + * needing to provide a custom implementation of their serialization strategy. + * + * @author John Blum + * @since 2.0.0 + */ +public interface SessionSerializer { + + /** + * Determines whether the given {@link Object} can be de/serialized by this {@link SessionSerializer}. + * + * @param obj {@link Object} to evaluate for whether de/serialization is supported. + * @return a boolean value indicating whether the specified {@link Object} can be de/serialized + * by this {@link SessionSerializer}. + * @see #canSerialize(Class) + */ + default boolean canSerialize(Object obj) { + return Optional.ofNullable(obj).map(Object::getClass).map(this::canSerialize).orElse(false); + } + + /** + * Determines whether the given {@link Class type} can be de/serialized by this {@link SessionSerializer}. + * + * @param type {@link Class} to evaluate for whether de/serialization is supported. + * @return a boolean value indicating whether the specified {@link Class type} can be de/serialized + * by this {@link SessionSerializer}. + * @see #canSerialize(Object) + */ + boolean canSerialize(Class type); + + /** + * Serializes the given {@link Object} to the provided {@code out} stream. + * + * @param session {@link Object} to serialize. + * @param out stream in which to write the bytes of the {@link Object}. + */ + void serialize(T session, OUT out); + + /** + * Deserializes an {@link Object} from bytes contained in the provided {@code in} stream. + * + * @param in stream from which to read the bytes of the {@link Object}. + * @return the deserialized {@link Object}. + */ + T deserialize(IN in); + +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/AbstractDataSerializableSessionSerializer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/AbstractDataSerializableSessionSerializer.java new file mode 100644 index 0000000..e68ebb2 --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/AbstractDataSerializableSessionSerializer.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017 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.serialization.data; + +import static java.util.Arrays.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Optional; + +import org.apache.geode.DataSerializer; + +import org.springframework.session.data.gemfire.serialization.SerializationException; +import org.springframework.session.data.gemfire.serialization.SessionSerializer; + +/** + * The {@link AbstractDataSerializableSessionSerializer} class... + * + * @author John Blum + * @see java.io.DataInput + * @see java.io.DataOutput + * @see org.apache.geode.DataSerializer + * @see org.springframework.session.data.gemfire.serialization.SessionSerializer + * @since 2.0.0 + */ +public abstract class AbstractDataSerializableSessionSerializer extends DataSerializer + implements SessionSerializer { + + @Override + public int getId() { + return 0x0A11ACE5; + } + + @Override + public Class[] getSupportedClasses() { + return new Class[0]; + } + + @Override + @SuppressWarnings("unchecked") + public boolean canSerialize(Class type) { + return stream(nullSafeArray(getSupportedClasses(), Class.class)) + .anyMatch(supportedClass -> supportedClass.isAssignableFrom(type)); + } + + @Override + @SuppressWarnings("unchecked") + public boolean toData(Object session, DataOutput out) throws IOException { + + return Optional.ofNullable(session) + .filter(this::canSerialize) + .map(it -> { + serialize((T) session, out); + return true; + }) + .orElse(false); + } + + @Override + public Object fromData(DataInput in) throws IOException, ClassNotFoundException { + return deserialize(in); + } + + protected T safeRead(DataInput in, DataInputReader reader) { + try { + return reader.doRead(in); + } + catch (ClassNotFoundException | IOException cause) { + throw new SerializationException(cause); + } + } + + protected void safeWrite(DataOutput out, DataOutputWriter writer) { + try { + writer.doWrite(out); + } + catch (IOException cause) { + throw new SerializationException(cause); + } + } + + protected interface DataInputReader { + T doRead(DataInput in) throws ClassNotFoundException, IOException; + } + + protected interface DataOutputWriter { + void doWrite(DataOutput out) throws IOException; + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/provider/DataSerializableSessionAttributesSerializer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/provider/DataSerializableSessionAttributesSerializer.java new file mode 100644 index 0000000..2b7fa16 --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/provider/DataSerializableSessionAttributesSerializer.java @@ -0,0 +1,83 @@ +/* + * Copyright 2017 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.serialization.data.provider; + +import static org.springframework.data.gemfire.util.ArrayUtils.asArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeSet; +import static org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes; + +import java.io.DataInput; +import java.io.DataOutput; +import java.util.Set; + +import org.apache.geode.DataSerializer; + +import org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer; + +/** + * The {@link DataSerializableSessionAttributesSerializer} class... + * + * @author John Blum + * @since 2.0.0 + */ +@SuppressWarnings("unused") +public class DataSerializableSessionAttributesSerializer + extends AbstractDataSerializableSessionSerializer { + + public static void register() { + register(DataSerializableSessionAttributesSerializer.class); + } + + @Override + public int getId() { + return 0x8192ACE5; + } + + @Override + public Class[] getSupportedClasses() { + return asArray(GemFireSessionAttributes.class); + } + + @Override + //@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public void serialize(GemFireSessionAttributes sessionAttributes, DataOutput out) { + + synchronized (sessionAttributes) { + + Set attributeNames = nullSafeSet(sessionAttributes.getAttributeNames()); + + safeWrite(out, output -> output.writeInt(attributeNames.size())); + + attributeNames.forEach(attributeName -> { + safeWrite(out, output -> output.writeUTF(attributeName)); + safeWrite(out, output -> writeObject(sessionAttributes.getAttribute(attributeName), output)); + }); + } + } + + @Override + public GemFireSessionAttributes deserialize(DataInput in) { + + GemFireSessionAttributes sessionAttributes = GemFireSessionAttributes.create(); + + for (int count = safeRead(in, DataInput::readInt); count > 0; count--) { + sessionAttributes.setAttribute(safeRead(in, DataInput::readUTF), safeRead(in, DataSerializer::readObject)); + } + + return sessionAttributes; + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/provider/DataSerializableSessionSerializer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/provider/DataSerializableSessionSerializer.java new file mode 100644 index 0000000..67d15d2 --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/provider/DataSerializableSessionSerializer.java @@ -0,0 +1,126 @@ +/* + * Copyright 2017 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.serialization.data.provider; + +import static org.springframework.data.gemfire.util.ArrayUtils.asArray; + +import java.io.DataInput; +import java.io.DataOutput; +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.Set; + +import org.apache.geode.DataSerializer; + +import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession; +import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes; +import org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer; +import org.springframework.session.data.gemfire.support.AbstractSession; +import org.springframework.util.StringUtils; + +/** + * The {@link DataSerializableSessionSerializer} class... + * + * @author John Blum + * @since 2.0.0 + */ +@SuppressWarnings("unused") +public class DataSerializableSessionSerializer extends AbstractDataSerializableSessionSerializer { + + public static void register() { + register(DataSerializableSessionSerializer.class); + DataSerializableSessionAttributesSerializer.register(); + } + + @Override + public int getId() { + return 0x4096ACE5; + } + + @Override + public Class[] getSupportedClasses() { + return asArray(GemFireSession.class); + } + + @Override + //@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public void serialize(GemFireSession session, DataOutput out) { + + synchronized (session) { + + safeWrite(out, output -> output.writeUTF(session.getId())); + safeWrite(out, output -> output.writeLong(session.getCreationTime().toEpochMilli())); + safeWrite(out, output -> output.writeLong(session.getLastAccessedTime().toEpochMilli())); + safeWrite(out, output -> output.writeLong(session.getMaxInactiveInterval().getSeconds())); + + String principalName = session.getPrincipalName(); + + int length = (StringUtils.hasText(principalName) ? principalName.length() : 0); + + safeWrite(out, output -> output.writeInt(length)); + + if (length > 0) { + safeWrite(out, output -> output.writeUTF(principalName)); + } + + safeWrite(out, output -> writeObject(session.getAttributes(), out)); + } + } + + @Override + public GemFireSession deserialize(DataInput in) { + + GemFireSession session = GemFireSession.from(new AbstractSession() { + + @Override + public String getId() { + return safeRead(in, DataInput::readUTF); + } + + @Override + public Instant getCreationTime() { + return safeRead(in, in -> Instant.ofEpochMilli(in.readLong())); + } + + @Override + public Instant getLastAccessedTime() { + return safeRead(in, in -> Instant.ofEpochMilli(in.readLong())); + } + + @Override + public Duration getMaxInactiveInterval() { + return safeRead(in, in -> Duration.ofSeconds(in.readLong())); + } + + @Override + public Set getAttributeNames() { + return Collections.emptySet(); + } + }); + + int principalNameLength = safeRead(in, DataInput::readInt); + + if (principalNameLength > 0) { + session.setPrincipalName(safeRead(in, DataInput::readUTF)); + } + + session.getAttributes().from(this.safeRead(in, DataSerializer::readObject)); + + return session; + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/support/DataSerializerSessionSerializerAdapter.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/support/DataSerializerSessionSerializerAdapter.java new file mode 100644 index 0000000..a643cfd --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/support/DataSerializerSessionSerializerAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright 2017 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.serialization.data.support; + +import static org.springframework.data.gemfire.util.ArrayUtils.asArray; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + +import java.io.DataInput; +import java.io.DataOutput; +import java.util.Optional; + +import javax.annotation.Resource; + +import org.springframework.session.Session; +import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; +import org.springframework.session.data.gemfire.serialization.SessionSerializer; + +/** + * The DataSerializerSessionSerializerAdapter class... + * + * @author John Blum + * @since 2.0.0 + */ +@SuppressWarnings("unused") +public class DataSerializerSessionSerializerAdapter extends WirableDataSerializer { + + static { + register(DataSerializerSessionSerializerAdapter.class); + } + + @Resource(name = "${" + GemFireHttpSessionConfiguration.SESSION_SERIALIZER_QUALIFIER_PROPERTY_NAME + + ":" + GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME + "}") + private SessionSerializer sessionSerializer; + + public DataSerializerSessionSerializerAdapter() { + autowire(); + } + + public DataSerializerSessionSerializerAdapter(SessionSerializer sessionSerializer) { + this.sessionSerializer = Optional.ofNullable(sessionSerializer) + .orElseThrow(() -> newIllegalArgumentException("SessionSerializer is required")); + } + + @Override + public int getId() { + return 0x0BAC2BAC; + } + + protected SessionSerializer getSessionSerializer() { + return Optional.ofNullable(this.sessionSerializer) + .orElseThrow(() -> newIllegalStateException("SessionSerializer was not properly configured")); + } + + @Override + public Class[] getSupportedClasses() { + return asArray(Session.class); + } + + @Override + public void serialize(T session, DataOutput out) { + getSessionSerializer().serialize(session, out); + } + + @Override + public T deserialize(DataInput in) { + return getSessionSerializer().deserialize(in); + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/support/WirableDataSerializer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/support/WirableDataSerializer.java new file mode 100644 index 0000000..894c071 --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/data/support/WirableDataSerializer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 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.serialization.data.support; + +import static org.springframework.data.gemfire.support.GemfireBeanFactoryLocator.newBeanFactoryLocator; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.wiring.BeanConfigurerSupport; +import org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer; + +/** + * The WirableDataSerializer class... + * + * @author John Blum + * @since 2.0.0 + */ +public abstract class WirableDataSerializer extends AbstractDataSerializableSessionSerializer { + + protected final void autowire() { + + BeanConfigurerSupport beanConfigurer = newBeanConfigurer(locateBeanFactory()); + + beanConfigurer.configureBean(this); + beanConfigurer.destroy(); + } + + private BeanFactory locateBeanFactory() { + return newBeanFactoryLocator().useBeanFactory(); + } + + private BeanConfigurerSupport newBeanConfigurer(BeanFactory beanFactory) { + + BeanConfigurerSupport beanConfigurer = new BeanConfigurerSupport(); + + beanConfigurer.setBeanFactory(beanFactory); + beanConfigurer.afterPropertiesSet(); + + return beanConfigurer; + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/AbstractPdxSerializableSessionSerializer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/AbstractPdxSerializableSessionSerializer.java new file mode 100644 index 0000000..e681b1d --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/AbstractPdxSerializableSessionSerializer.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017 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.serialization.pdx; + +import java.util.Optional; + +import org.apache.geode.pdx.PdxReader; +import org.apache.geode.pdx.PdxSerializer; +import org.apache.geode.pdx.PdxWriter; + +import org.springframework.session.Session; +import org.springframework.session.data.gemfire.serialization.SessionSerializer; + +/** + * The {@link AbstractPdxSerializableSessionSerializer} class... + * + * @author John Blum + * @since 2.0.0 + */ +public abstract class AbstractPdxSerializableSessionSerializer + implements PdxSerializer, SessionSerializer { + + @Override + public boolean canSerialize(Class type) { + return Optional.ofNullable(type).map(Session.class::isAssignableFrom).orElse(false); + } + + @Override + @SuppressWarnings("unchecked") + public boolean toData(Object session, PdxWriter writer) { + + return Optional.ofNullable(session) + .filter(this::canSerialize) + .map(it -> { + serialize((T) session, writer); + return true; + }) + .orElse(false); + } + + @Override + public Object fromData(Class type, PdxReader reader) { + + return Optional.ofNullable(type) + .filter(this::canSerialize) + .map(it -> deserialize(reader)) + .orElse(null); + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/provider/PdxSerializableSessionSerializer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/provider/PdxSerializableSessionSerializer.java new file mode 100644 index 0000000..4158360 --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/provider/PdxSerializableSessionSerializer.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 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.serialization.pdx.provider; + +import static org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession; +import static org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import org.apache.geode.pdx.PdxReader; +import org.apache.geode.pdx.PdxWriter; + +import org.springframework.session.data.gemfire.serialization.pdx.AbstractPdxSerializableSessionSerializer; +import org.springframework.session.data.gemfire.support.AbstractSession; + +/** + * The PdxSerializableSessionSerializer class... + * + * @author John Blum + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class PdxSerializableSessionSerializer extends AbstractPdxSerializableSessionSerializer { + + @Override + public boolean canSerialize(Class type) { + return Optional.ofNullable(type).map(GemFireSession.class::isAssignableFrom).orElse(false); + } + + @Override + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public void serialize(GemFireSession session, PdxWriter writer) { + + synchronized (session) { + + writer.writeString("id", session.getId()); + writer.writeLong("creationTime", session.getCreationTime().toEpochMilli()); + writer.writeLong("lastAccessedTime", session.getLastAccessedTime().toEpochMilli()); + writer.writeLong("maxInactiveIntervalInSeconds", session.getMaxInactiveInterval().getSeconds()); + writer.writeString("principalName", session.getPrincipalName()); + writer.writeObject("attributes", session.getAttributes()); + } + } + + @Override + public GemFireSession deserialize(PdxReader reader) { + + GemFireSession session = GemFireSession.from(new AbstractSession() { + + @Override + public String getId() { + return reader.readString("id"); + } + + @Override + public Instant getCreationTime() { + return Instant.ofEpochMilli(reader.readLong("creationTime")); + } + + @Override + public Instant getLastAccessedTime() { + return Instant.ofEpochMilli(reader.readLong("lastAccessedTime")); + } + + @Override + public Duration getMaxInactiveInterval() { + return Duration.ofSeconds(reader.readLong("maxInactiveIntervalInSeconds")); + } + + @Override + public Set getAttributeNames() { + return Collections.emptySet(); + } + }); + + session.setPrincipalName(reader.readString("principalName")); + session.getAttributes().from((GemFireSessionAttributes) reader.readObject("attributes")); + + return session; + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/support/ComposablePdxSerializer.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/support/ComposablePdxSerializer.java new file mode 100644 index 0000000..669651c --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/support/ComposablePdxSerializer.java @@ -0,0 +1,98 @@ +/* + * Copyright 2017 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.serialization.pdx.support; + +import static java.util.stream.StreamSupport.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.geode.pdx.PdxReader; +import org.apache.geode.pdx.PdxSerializer; +import org.apache.geode.pdx.PdxWriter; + +/** + * The ComposablePdxSerializer class... + * + * @author John Blum + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class ComposablePdxSerializer implements Iterable, PdxSerializer { + + private final List pdxSerializers; + + private ComposablePdxSerializer(List pdxSerializers) { + + this.pdxSerializers = Optional.ofNullable(pdxSerializers) + .map(it -> Collections.unmodifiableList(pdxSerializers)) + .orElseThrow(() -> newIllegalArgumentException("PdxSerializers [%s] are required", pdxSerializers)); + } + + public static PdxSerializer compose(PdxSerializer... pdxSerializers) { + return compose(Arrays.asList(nullSafeArray(pdxSerializers, PdxSerializer.class))); + } + + public static PdxSerializer compose(Iterable pdxSerializers) { + + List pdxSerializerList = + stream(nullSafeIterable(pdxSerializers).spliterator(), false).collect(Collectors.toList()); + + return (pdxSerializerList.isEmpty() ? null + : (pdxSerializerList.size() == 1 ? pdxSerializerList.get(0) + : new ComposablePdxSerializer(pdxSerializerList))); + } + + @Override + public Iterator iterator() { + return this.pdxSerializers.iterator(); + } + + @Override + public boolean toData(Object obj, PdxWriter out) { + + for (PdxSerializer pdxSerializer : this) { + if (pdxSerializer.toData(obj, out)) { + return true; + } + } + + return false; + } + + @Override + public Object fromData(Class type, PdxReader in) { + + for (PdxSerializer pdxSerializer : this) { + + Object obj = pdxSerializer.fromData(type, in); + + if (obj != null) { + return obj; + } + } + + return null; + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/support/PdxSerializerSessionSerializerAdapter.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/support/PdxSerializerSessionSerializerAdapter.java new file mode 100644 index 0000000..80929bc --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/serialization/pdx/support/PdxSerializerSessionSerializerAdapter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017 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.serialization.pdx.support; + +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + +import java.util.Optional; + +import org.apache.geode.pdx.PdxReader; +import org.apache.geode.pdx.PdxWriter; + +import org.springframework.session.Session; +import org.springframework.session.data.gemfire.serialization.SessionSerializer; +import org.springframework.session.data.gemfire.serialization.pdx.AbstractPdxSerializableSessionSerializer; + +/** + * The {@link PdxSerializerSessionSerializerAdapter} class... + * + * @author John Blum + * @since 2.0.0 + */ +@SuppressWarnings("unused") +public class PdxSerializerSessionSerializerAdapter + extends AbstractPdxSerializableSessionSerializer { + + private final SessionSerializer sessionSerializer; + + public PdxSerializerSessionSerializerAdapter(SessionSerializer sessionSerializer) { + this.sessionSerializer = Optional.ofNullable(sessionSerializer) + .orElseThrow(() -> newIllegalArgumentException("SessionSerializer is required")); + } + + protected SessionSerializer getSessionSerializer() { + return this.sessionSerializer; + } + + @Override + public void serialize(T session, PdxWriter writer) { + getSessionSerializer().serialize(session, writer); + } + + @Override + public T deserialize(PdxReader reader) { + return getSessionSerializer().deserialize(reader); + } +} diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/AbstractSession.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/AbstractSession.java new file mode 100644 index 0000000..5e080ec --- /dev/null +++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/AbstractSession.java @@ -0,0 +1,102 @@ +/* + * Copyright 2017 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.time.Duration; +import java.time.Instant; +import java.util.Set; + +import org.springframework.session.Session; + +/** + * The AbstractSession class... + * + * @author John Blum + * @since 1.0.0 + */ +public class AbstractSession implements Session { + + @Override + public String getId() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public void setAttribute(String attributeName, Object attributeValue) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public T getAttribute(String attributeName) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public T getAttributeOrDefault(String name, T defaultValue) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public T getRequiredAttribute(String name) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public Set getAttributeNames() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public boolean isExpired() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public Instant getCreationTime() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public void setLastAccessedTime(Instant lastAccessedTime) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public Instant getLastAccessedTime() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public void setMaxInactiveInterval(Duration interval) { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public Duration getMaxInactiveInterval() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public String changeSessionId() { + throw new UnsupportedOperationException("Not Implemented"); + } + + @Override + public void removeAttribute(String attributeName) { + throw new UnsupportedOperationException("Not Implemented"); + } +} diff --git a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTest.java b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTest.java index 77aee9f..23dd3c4 100644 --- a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTest.java +++ b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTest.java @@ -157,7 +157,7 @@ public class GemFireHttpSessionConfigurationTest { public void setAndGetPoolName() { assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo( - GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME); + GemFireHttpSessionConfiguration.DEFAULT_POOL_NAME); this.gemfireConfiguration.setPoolName("TestPoolName"); @@ -166,17 +166,17 @@ public class GemFireHttpSessionConfigurationTest { this.gemfireConfiguration.setPoolName(" "); assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo( - GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME); + GemFireHttpSessionConfiguration.DEFAULT_POOL_NAME); this.gemfireConfiguration.setPoolName(""); assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo( - GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME); + GemFireHttpSessionConfiguration.DEFAULT_POOL_NAME); this.gemfireConfiguration.setPoolName(null); assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo( - GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME); + GemFireHttpSessionConfiguration.DEFAULT_POOL_NAME); } @Test @@ -199,27 +199,27 @@ public class GemFireHttpSessionConfigurationTest { @Test public void setAndGetSpringSessionGemFireRegionName() { - assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo( - GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + assertThat(this.gemfireConfiguration.getSessionRegionName()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME); - this.gemfireConfiguration.setSpringSessionGemFireRegionName("test"); + this.gemfireConfiguration.setSessionRegionName("test"); - assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("test"); + assertThat(this.gemfireConfiguration.getSessionRegionName()).isEqualTo("test"); - this.gemfireConfiguration.setSpringSessionGemFireRegionName(" "); + this.gemfireConfiguration.setSessionRegionName(" "); - assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo( - GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + assertThat(this.gemfireConfiguration.getSessionRegionName()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME); - this.gemfireConfiguration.setSpringSessionGemFireRegionName(""); + this.gemfireConfiguration.setSessionRegionName(""); - assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo( - GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + assertThat(this.gemfireConfiguration.getSessionRegionName()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME); - this.gemfireConfiguration.setSpringSessionGemFireRegionName(null); + this.gemfireConfiguration.setSessionRegionName(null); - assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo( - GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + assertThat(this.gemfireConfiguration.getSessionRegionName()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SESSION_REGION_NAME); } @Test @@ -246,7 +246,7 @@ public class GemFireHttpSessionConfigurationTest { assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(600); assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo("TestPool"); assertThat(this.gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE); - assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("TEST"); + assertThat(this.gemfireConfiguration.getSessionRegionName()).isEqualTo("TEST"); verify(mockAnnotationMetadata, times(1)).getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName())); } @@ -276,11 +276,11 @@ public class GemFireHttpSessionConfigurationTest { given(mockGemFireCache.getRegion(eq("Example"))).willReturn(mockRegion); - this.gemfireConfiguration.setSpringSessionGemFireRegionName("Example"); + this.gemfireConfiguration.setSessionRegionName("Example"); GemfireTemplate template = this.gemfireConfiguration.sessionRegionTemplate(mockGemFireCache); - assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("Example"); + assertThat(this.gemfireConfiguration.getSessionRegionName()).isEqualTo("Example"); assertThat(template).isNotNull(); assertThat(template.getRegion()).isSameAs(mockRegion); @@ -298,7 +298,7 @@ public class GemFireHttpSessionConfigurationTest { this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY); this.gemfireConfiguration.setPoolName("TestPool"); this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PERSISTENT); - this.gemfireConfiguration.setSpringSessionGemFireRegionName("TestRegion"); + this.gemfireConfiguration.setSessionRegionName("TestRegion"); GemFireCacheTypeAwareRegionFactoryBean sessionRegionFactoryBean = this.gemfireConfiguration.sessionRegion(mockGemFireCache, mockRegionAttributes); @@ -339,9 +339,9 @@ public class GemFireHttpSessionConfigurationTest { assertThat(sessionRegionAttributes).isNotNull(); assertThat(sessionRegionAttributes.getKeyConstraint()).isEqualTo( - GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT); + GemFireHttpSessionConfiguration.SESSION_REGION_KEY_CONSTRAINT); assertThat(sessionRegionAttributes.getValueConstraint()).isEqualTo( - GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT); + GemFireHttpSessionConfiguration.SESSION_REGION_VALUE_CONSTRAINT); ExpirationAttributes entryIdleTimeoutExpiration = sessionRegionAttributes.getEntryIdleTimeout(); @@ -369,9 +369,9 @@ public class GemFireHttpSessionConfigurationTest { assertThat(sessionRegionAttributes).isNotNull(); assertThat(sessionRegionAttributes.getKeyConstraint()).isEqualTo( - GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT); + GemFireHttpSessionConfiguration.SESSION_REGION_KEY_CONSTRAINT); assertThat(sessionRegionAttributes.getValueConstraint()).isEqualTo( - GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT); + GemFireHttpSessionConfiguration.SESSION_REGION_VALUE_CONSTRAINT); ExpirationAttributes entryIdleTimeoutExpiration = sessionRegionAttributes.getEntryIdleTimeout(); diff --git a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBeanTest.java b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBeanTest.java index 9cfddd8..02bd463 100644 --- a/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBeanTest.java +++ b/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBeanTest.java @@ -243,7 +243,7 @@ public class GemFireCacheTypeAwareRegionFactoryBeanTest { public void setAndGetPoolName() { assertThat(this.regionFactoryBean.getPoolName()).isEqualTo( - GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_GEMFIRE_POOL_NAME); + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_POOL_NAME); this.regionFactoryBean.setPoolName("TestPoolName"); @@ -252,17 +252,17 @@ public class GemFireCacheTypeAwareRegionFactoryBeanTest { this.regionFactoryBean.setPoolName(" "); assertThat(this.regionFactoryBean.getPoolName()).isEqualTo( - GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_GEMFIRE_POOL_NAME); + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_POOL_NAME); this.regionFactoryBean.setPoolName(""); assertThat(this.regionFactoryBean.getPoolName()).isEqualTo( - GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_GEMFIRE_POOL_NAME); + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_POOL_NAME); this.regionFactoryBean.setPoolName(null); assertThat(this.regionFactoryBean.getPoolName()).isEqualTo( - GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_GEMFIRE_POOL_NAME); + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_POOL_NAME); } @Test @@ -286,7 +286,7 @@ public class GemFireCacheTypeAwareRegionFactoryBeanTest { public void setAndGetRegionName() { assertThat(this.regionFactoryBean.getRegionName()).isEqualTo( - GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SESSION_REGION_NAME); this.regionFactoryBean.setRegionName("Example"); @@ -295,17 +295,17 @@ public class GemFireCacheTypeAwareRegionFactoryBeanTest { this.regionFactoryBean.setRegionName(" "); assertThat(this.regionFactoryBean.getRegionName()).isEqualTo( - GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SESSION_REGION_NAME); this.regionFactoryBean.setRegionName(""); assertThat(this.regionFactoryBean.getRegionName()).isEqualTo( - GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SESSION_REGION_NAME); this.regionFactoryBean.setRegionName(null); assertThat(this.regionFactoryBean.getRegionName()).isEqualTo( - GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SESSION_REGION_NAME); } @Test diff --git a/spring-session-data-geode/src/test/resources/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionXmlConfigurationTests-context.xml b/spring-session-data-geode/src/test/resources/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionXmlConfigurationTests-context.xml index 6ccf874..4ec2fdf 100644 --- a/spring-session-data-geode/src/test/resources/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionXmlConfigurationTests-context.xml +++ b/spring-session-data-geode/src/test/resources/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionXmlConfigurationTests-context.xml @@ -21,6 +21,6 @@ + p:sessionRegionName="XmlExample"/>