org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration.java

Source

/*
 * Copyright 2019 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
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.session.data.gemfire.config.annotation.web.http;

import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;

import javax.annotation.PostConstruct;

import org.apache.geode.DataSerializer;
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;
import org.apache.geode.pdx.PdxSerializer;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
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.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.GemfireOperations;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.IndexFactoryBean;
import org.springframework.data.gemfire.IndexType;
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.data.gemfire.util.RegionUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
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.SessionAttributesIndexFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionCacheTypeAwareRegionFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer;
import org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy;
import org.springframework.session.data.gemfire.expiration.config.SessionExpirationTimeoutAwareBeanPostProcessor;
import org.springframework.session.data.gemfire.expiration.support.SessionExpirationPolicyCustomExpiryAdapter;
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.data.support.DataSerializerSessionSerializerAdapter;
import org.springframework.session.data.gemfire.serialization.pdx.provider.PdxSerializableSessionSerializer;
import org.springframework.session.data.gemfire.serialization.pdx.support.ComposablePdxSerializer;
import org.springframework.session.data.gemfire.serialization.pdx.support.PdxSerializerSessionSerializerAdapter;
import org.springframework.session.data.gemfire.support.DeltaAwareDirtyPredicate;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.session.data.gemfire.support.IsDirtyPredicate;
import org.springframework.util.StringUtils;

/**
 * The {@link GemFireHttpSessionConfiguration} class is a Spring {@link Configuration @Configuration} class
 * used to configure and initialize Pivotal GemFire/Apache Geode as a clustered, distributed and replicated
 * {@link javax.servlet.http.HttpSession} provider implementation in Spring {@link Session}.
 *
 * @author John Blum
 * @see java.time.Duration
 * @see org.apache.geode.DataSerializer
 * @see org.apache.geode.cache.Cache
 * @see org.apache.geode.cache.ExpirationAttributes
 * @see org.apache.geode.cache.GemFireCache
 * @see org.apache.geode.cache.Region
 * @see org.apache.geode.cache.RegionAttributes
 * @see org.apache.geode.cache.RegionShortcut
 * @see org.apache.geode.cache.client.ClientCache
 * @see org.apache.geode.cache.client.ClientRegionShortcut
 * @see org.apache.geode.cache.client.Pool
 * @see org.apache.geode.pdx.PdxSerializer
 * @see org.springframework.beans.factory.config.BeanPostProcessor
 * @see org.springframework.context.annotation.Bean
 * @see org.springframework.context.annotation.Configuration
 * @see org.springframework.context.annotation.DependsOn
 * @see org.springframework.context.annotation.Import
 * @see org.springframework.context.annotation.ImportAware
 * @see org.springframework.core.annotation.AnnotationAttributes
 * @see org.springframework.core.type.AnnotationMetadata
 * @see org.springframework.data.gemfire.CacheFactoryBean
 * @see org.springframework.data.gemfire.GemfireOperations
 * @see org.springframework.data.gemfire.GemfireTemplate
 * @see org.springframework.data.gemfire.IndexFactoryBean
 * @see org.springframework.data.gemfire.RegionAttributesFactoryBean
 * @see org.springframework.session.Session
 * @see org.springframework.session.SessionRepository
 * @see org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration
 * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
 * @see org.springframework.session.data.gemfire.config.annotation.web.http.AbstractGemFireHttpSessionConfiguration
 * @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
 * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionCacheTypeAwareRegionFactoryBean
 * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionAttributesIndexFactoryBean
 * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer
 * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy
 * @see org.springframework.session.data.gemfire.expiration.config.SessionExpirationTimeoutAwareBeanPostProcessor
 * @see org.springframework.session.data.gemfire.expiration.support.SessionExpirationPolicyCustomExpiryAdapter
 * @see org.springframework.session.data.gemfire.serialization.SessionSerializer
 * @since 1.1.0
 */
@Configuration
@SuppressWarnings("unused")
public class GemFireHttpSessionConfiguration extends AbstractGemFireHttpSessionConfiguration
        implements ImportAware {

    /**
     * Default expose Spring Session using Apache Geode or Pivotal GemFire configuration as {@link Properties}
     * in Spring's {@link Environment}.
     */
    public static final boolean DEFAULT_EXPOSE_CONFIGURATION_AS_PROPERTIES = false;

    /**
     * Indicates whether to employ Apache Geode/Pivotal's DataSerialization framework
     * for {@link Session} de/serialization.
     */
    public static final boolean DEFAULT_USE_DATA_SERIALIZATION = false;

    /**
     * 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);

    /**
     * Key and Value class type constraints applied to the {@link Session} {@link Region}.
     */
    protected static final Class<Object> SESSION_REGION_KEY_CONSTRAINT = Object.class;
    protected static final Class<GemFireSession> SESSION_REGION_VALUE_CONSTRAINT = GemFireSession.class;

    /**
     * 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;

    /**
     * Default {@link IsDirtyPredicate} strategy interface used to determine whether the users' application
     * domain objects are dirty or not.
     */
    public static final IsDirtyPredicate DEFAULT_IS_DIRTY_PREDICATE = DeltaAwareDirtyPredicate.INSTANCE;

    /**
     * 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;

    /**
     * {@link SpringSessionGemFireConfigurer} {@link Class Interface} {@link Method} {@link String Names}
     */
    public static final String CONFIGURER_GET_CLIENT_REGION_SHORTCUT_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getClientRegionShortcut");

    public static final String CONFIGURER_GET_EXPOSE_CONFIGURATION_IN_PROPERTIES_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getExposeConfigurationAsProperties");

    public static final String CONFIGURER_GET_INDEXABLE_SESSION_ATTRIBUTES_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getIndexableSessionAttributes");

    public static final String CONFIGURER_GET_MAX_INACTIVE_INTERVAL_IN_SECONDS_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getMaxInactiveIntervalInSeconds");

    public static final String CONFIGURER_GET_POOL_NAME_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getPoolName");

    public static final String CONFIGURER_GET_REGION_NAME_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getRegionName");

    public static final String CONFIGURER_GET_SERVER_REGION_SHORTCUT_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getServerRegionShortcut");

    public static final String CONFIGURER_GET_SESSION_EXPIRATION_POLICY_BEAN_NAME_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getSessionExpirationPolicyBeanName");

    public static final String CONFIGURER_GET_SESSION_SERIALIZER_BEAN_NAME_METHOD_NAME = findByMethodName(
            SpringSessionGemFireConfigurer.class, "getSessionSerializerBeanName");

    /**
     * Name of the connection {@link Pool} used by the client {@link Region} to send {@link Session} state
     * to the cluster of  Apache Geode servers.
     */
    public static final String DEFAULT_POOL_NAME = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME;

    /**
     * Default name for the {@link SessionExpirationPolicy} bean.
     */
    public static final String DEFAULT_SESSION_EXPIRATION_POLICY_BEAN_NAME = "";

    /**
     * Default name of (Client)Cache {@link Region} used to store {@link Session} state.
     */
    public static final String DEFAULT_SESSION_REGION_NAME = "ClusteredSpringSessions";

    /**
     * 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_BEAN_ALIAS = "SessionSerializerRegisteredBeanAlias";

    public static final String DEFAULT_SESSION_SERIALIZER_BEAN_NAME = SESSION_PDX_SERIALIZER_BEAN_NAME;

    protected static final String SPRING_SESSION_GEMFIRE_PROPERTY_SOURCE = GemFireHttpSessionConfiguration.class
            .getName().concat(".PROPERTY_SOURCE");

    /**
     * Defaults names of all {@link Session} attributes that will be indexed by Apache Geode.
     */
    public static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = {};

    private boolean exposeConfigurationAsProperties = DEFAULT_EXPOSE_CONFIGURATION_AS_PROPERTIES;
    private boolean usingDataSerialization = DEFAULT_USE_DATA_SERIALIZATION;

    private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;

    private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT;

    private IsDirtyPredicate dirtyPredicate = DEFAULT_IS_DIRTY_PREDICATE;

    private RegionShortcut serverRegionShortcut = DEFAULT_SERVER_REGION_SHORTCUT;

    private String poolName = DEFAULT_POOL_NAME;

    private String sessionExpirationPolicyBeanName = DEFAULT_SESSION_EXPIRATION_POLICY_BEAN_NAME;

    private String sessionRegionName = DEFAULT_SESSION_REGION_NAME;

    private String sessionSerializerBeanName = DEFAULT_SESSION_SERIALIZER_BEAN_NAME;

    private String[] indexableSessionAttributes = DEFAULT_INDEXABLE_SESSION_ATTRIBUTES;

    private static @NonNull String findByMethodName(@NonNull Class<?> type, @NonNull String methodName) {

        return Arrays.stream(type.getDeclaredMethods()).map(Method::getName)
                .filter(declaredMethodName -> declaredMethodName.startsWith(methodName)).findFirst().orElseThrow(
                        () -> newIllegalArgumentException("No method with name [%1$s] was found on class [%2$s]",
                                methodName, type.getName()));
    }

    private static Optional<String> safeFindByMethodName(@NonNull Class<?> type, @NonNull String methodName) {

        try {
            return Optional.of(findByMethodName(type, methodName));
        } catch (Throwable ignore) {
            return Optional.empty();
        }
    }

    private static boolean isOverriddenMethodPresent(@Nullable Object target, @Nullable String methodName) {

        return Optional.ofNullable(target).map(Object::getClass)
                .flatMap(targetType -> safeFindByMethodName(targetType, methodName)).isPresent();
    }

    /**
     * Gets the {@link ClientRegionShortcut} used to configure the data management policy of the {@link ClientCache}
     * {@link Region} that will store {@link Session} state.
     *
     * 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) {
        this.clientRegionShortcut = shortcut;
    }

    /**
     * Gets the {@link ClientRegionShortcut} used to configure the data management policy of the {@link ClientCache}
     * {@link Region} that will store {@link Session} state.
     *
     * 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
     */
    public ClientRegionShortcut getClientRegionShortcut() {

        return this.clientRegionShortcut != null ? this.clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT;
    }

    /**
     * Sets whether to expose the configuration of Spring Session using Apache Geode or Pivotal GemFire
     * as {@link Properties} in the Spring {@link Environment}.
     *
     * @param exposeConfigurationAsProperties boolean indicating whether to expose the configuration
     * of Spring Session using Apache Geode or Pivotal GemFire as {@link Properties} in the Spring {@link Environment}.
     *
     * @see EnableGemFireHttpSession#exposeConfigurationAsProperties()
     */
    public void setExposeConfigurationAsProperties(boolean exposeConfigurationAsProperties) {
        this.exposeConfigurationAsProperties = exposeConfigurationAsProperties;
    }

    /**
     * Determines whether the configuration for Spring Session using Apache Geode or Pivotal GemFire should be exposed
     * in the Spring {@link org.springframework.core.env.Environment} as {@link Properties}.
     *
     * Currently, users may configure Spring Session for Apache Geode or Pivotal GemFire using attributes on this
     * {@link Annotation}, using the well-known and documented {@link Properties}
     * (e.g. {@literal spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds})
     * or using the {@link SpringSessionGemFireConfigurer} declared as a bean in the Spring application context.
     *
     * The {@link Properties} that are exposed will use the well-known property {@link String names} that are documented
     * in this {@link Annotation Annotation's} attributes.
     *
     * The values of the resulting {@link Properties} follows the precedence as outlined in the documentation:
     * first any {@link SpringSessionGemFireConfigurer} bean defined takes precedence, followed by explicit
     * {@link Properties} declared in Spring Boot {@literal application.properties} and finally, this
     * {@link Annotation Annotation's} attribute values.
     *
     * Defaults to {@literal false}.
     *
     * Use {@literal spring.session.data.gemfire.session.configuration.expose} in Spring Boot
     * {@literal application.properties}.
     *
     * @return a boolean value indicating whether to expose the configuration of Spring Session using Apache Geode
     * or Pivotal GemFire in the Spring {@link org.springframework.core.env.Environment} as {@link Properties}.
     */
    public boolean isExposeConfigurationAsProperties() {
        return this.exposeConfigurationAsProperties;
    }

    /**
     * Sets the names of all {@link Session} attributes that will be indexed.
     *
     * @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 {@link Session} attributes that will be indexed.
     *
     * @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.
     */
    public String[] getIndexableSessionAttributes() {

        return this.indexableSessionAttributes != null ? this.indexableSessionAttributes
                : DEFAULT_INDEXABLE_SESSION_ATTRIBUTES;
    }

    /**
     * Configures the {@link IsDirtyPredicate} strategy interface, as a bean from the Spring context, used to
     * determine whether the users' application domain objects are dirty or not.
     *
     * @param dirtyPredicate {@link IsDirtyPredicate} strategy interface bean used to determine whether
     * the users' application domain objects are dirty or not.
     * @see org.springframework.session.data.gemfire.support.IsDirtyPredicate
     */
    @Autowired(required = false)
    public void setIsDirtyPredicate(IsDirtyPredicate dirtyPredicate) {
        this.dirtyPredicate = dirtyPredicate;
    }

    /**
     * Returns the configured {@link IsDirtyPredicate} strategy interface bean, declared in the Spring context,
     * used to determine whether the users' application domain objects are dirty or not.
     *
     * Defaults to {@link GemFireHttpSessionConfiguration#DEFAULT_IS_DIRTY_PREDICATE}.
     *
     * @return the configured {@link IsDirtyPredicate} strategy interface bean used to determine whether
     * the users' application domain objects are dirty or not.
     * @see org.springframework.session.data.gemfire.support.IsDirtyPredicate
     */
    public IsDirtyPredicate getIsDirtyPredicate() {

        return this.dirtyPredicate != null ? this.dirtyPredicate : DEFAULT_IS_DIRTY_PREDICATE;
    }

    /**
     * Sets the maximum interval in seconds in which a {@link Session} can remain inactive before it expires.
     *
     * @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 expires.
     *
     * @return an integer value specifying the maximum interval in seconds that a {@link Session} can remain inactive
     * before it expires.
     */
    public int getMaxInactiveIntervalInSeconds() {
        return this.maxInactiveIntervalInSeconds;
    }

    /**
     * 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 {@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 {@link Pool} used by the client {@link Region} to send {@link Session}
     * to the cluster of servers during cache operations.
     *
     * @return a {@link String} containing the name of a {@link Pool}.
     * @see org.apache.geode.cache.client.Pool#getName()
     */
    public String getPoolName() {

        return StringUtils.hasText(this.poolName) ? this.poolName : DEFAULT_POOL_NAME;
    }

    /**
     * Sets the {@link RegionShortcut} used to configure the data management policy of the {@link Cache} {@link Region}
     * that will store {@link Session} state.
     *
     * 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) {
        this.serverRegionShortcut = shortcut;
    }

    /**
     * Gets the {@link RegionShortcut} used to configure the data management policy of the {@link Cache} {@link Region}
     * that will store {@link Session} state.
     *
     * 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
     */
    public RegionShortcut getServerRegionShortcut() {

        return this.serverRegionShortcut != null ? this.serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT;
    }

    /**
     * Sets the {@link String name} of the bean configured in the Spring application context implementing
     * the {@link SessionExpirationPolicy} for {@link Session} expiration.
     *
     * @param sessionExpirationPolicyBeanName {@link String} containing the name of the bean configured in
     * the Spring application context implementing the {@link SessionExpirationPolicy} for {@link Session} expiration.
     */
    public void setSessionExpirationPolicyBeanName(String sessionExpirationPolicyBeanName) {
        this.sessionExpirationPolicyBeanName = sessionExpirationPolicyBeanName;
    }

    /**
     * Returns an {@link Optional} {@link String name} of the bean configured in the Spring application context
     * implementing the {@link SessionExpirationPolicy} for {@link Session} expiration.
     *
     * @return an {@link Optional} {@link String name} of the bean configured in the Spring application context
     * implementing the {@link SessionExpirationPolicy} for {@link Session} expiration.
     */
    public Optional<String> getSessionExpirationPolicyBeanName() {

        return Optional.ofNullable(this.sessionExpirationPolicyBeanName).filter(StringUtils::hasText);
    }

    /**
     * Sets the name of the (Client)Cache {@link Region} used to store {@link Session} state.
     *
     * @param sessionRegionName {@link String} specifying the name of the (Client)Cache {@link Region}
     * used to store {@link Session} state.
     * @see EnableGemFireHttpSession#regionName()
     */
    public void setSessionRegionName(String sessionRegionName) {
        this.sessionRegionName = sessionRegionName;
    }

    /**
     * Returns the name of the (Client)Cache {@link Region} used to store {@link Session} state.
     *
     * @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()
     */
    public String getSessionRegionName() {

        return StringUtils.hasText(this.sessionRegionName) ? this.sessionRegionName : 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
     */
    public String getSessionSerializerBeanName() {

        return StringUtils.hasText(this.sessionSerializerBeanName) ? this.sessionSerializerBeanName
                : DEFAULT_SESSION_SERIALIZER_BEAN_NAME;
    }

    /**
     * Set whether to use Apache Geode / Pivotal GemFire's DataSerialization framework
     * for {@link Session} de/serialization.
     *
     * @param useDataSerialization boolean value indicating whether to use Apache Geode
     * / Pivotal GemFire's DataSerialization framework for {@link Session} de/serialization.
     */
    private void setUseDataSerialization(boolean useDataSerialization) {
        this.usingDataSerialization = useDataSerialization;
    }

    /**
     * Determine whether the configured serialization strategy is using Apache Geode / Pivotal GemFire's
     * DataSerialization framework.
     *
     * @return a boolean value indicating whether the configured serialization strategy is using Apache Geode
     * / Pivotal GemFire's DataSerialization framework.
     * @see #getSessionSerializerBeanName()
     */
    protected boolean isUsingDataSerialization() {
        return this.usingDataSerialization
                || SESSION_DATA_SERIALIZER_BEAN_NAME.equals(getSessionSerializerBeanName());
    }

    /**
     * Callback with the {@link AnnotationMetadata} of the class containing {@link Import @Import} annotation
     * that imported this {@link Configuration @Configuration} class.
     *
     * The {@link Configuration @Configuration} class should also be annotated with {@link EnableGemFireHttpSession}.
     *
     * @param importMetadata {@link AnnotationMetadata} of the application class importing
     * this {@link Configuration} class.
     * @see org.springframework.core.type.AnnotationMetadata
     * @see #applySpringSessionGemFireConfigurer()
     * @see #exposeSpringSessionGemFireConfiguration()
     */
    public void setImportMetadata(AnnotationMetadata importMetadata) {

        AnnotationAttributes enableGemFireHttpSessionAttributes = AnnotationAttributes
                .fromMap(importMetadata.getAnnotationAttributes(EnableGemFireHttpSession.class.getName()));

        // Apply configuration from {@link EnableGemFireHttpSession} annotation
        // and well-known, documented {@link Properties}.
        configureClientRegionShortcut(enableGemFireHttpSessionAttributes);
        configureExposeConfigurationAsProperties(enableGemFireHttpSessionAttributes);
        configureIndexedSessionAttributes(enableGemFireHttpSessionAttributes);
        configureMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAttributes);
        configurePoolName(enableGemFireHttpSessionAttributes);
        configureServerRegionShortcut(enableGemFireHttpSessionAttributes);
        configureSessionExpirationPolicyBeanName(enableGemFireHttpSessionAttributes);
        configureSessionRegionName(enableGemFireHttpSessionAttributes);
        configureSessionSerializerBeanName(enableGemFireHttpSessionAttributes);

        // Apply configuration from {@link SpringSessionGemFireConfigurer}.
        applySpringSessionGemFireConfigurer();

        // Expose configuration as {@link Properties} in the Spring {@link Environment}
        // if {@link EnableGemFireHttpSession#exposeConfigurationAsProperties} is set to {@literal true}.
        exposeSpringSessionGemFireConfiguration();
    }

    private void configureClientRegionShortcut(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        ClientRegionShortcut defaultClientRegionShortcut = enableGemFireHttpSessionAttributes
                .getEnum("clientRegionShortcut");

        setClientRegionShortcut(resolveProperty(clientRegionShortcutPropertyName(), ClientRegionShortcut.class,
                defaultClientRegionShortcut));
    }

    private void configureExposeConfigurationAsProperties(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        boolean defaultExposeConfigurationAsProperties = Boolean.TRUE
                .equals(enableGemFireHttpSessionAttributes.getBoolean("exposeConfigurationAsProperties"));

        setExposeConfigurationAsProperties(resolveProperty(exposeConfigurationAsPropertiesPropertyName(),
                defaultExposeConfigurationAsProperties));
    }

    private void configureIndexedSessionAttributes(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        String[] defaultIndexedSessionAttributes = enableGemFireHttpSessionAttributes
                .getStringArray("indexableSessionAttributes");

        setIndexableSessionAttributes(resolveProperty(indexedSessionAttributesPropertyName(),
                resolveProperty(indexableSessionAttributesPropertyName(), defaultIndexedSessionAttributes)));
    }

    private void configureMaxInactiveIntervalInSeconds(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        Integer defaultMaxInactiveIntervalInSeconds = enableGemFireHttpSessionAttributes
                .getNumber("maxInactiveIntervalInSeconds").intValue();

        setMaxInactiveIntervalInSeconds(
                resolveProperty(maxInactiveIntervalInSecondsPropertyName(), defaultMaxInactiveIntervalInSeconds));
    }

    private void configurePoolName(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        String defaultPoolName = enableGemFireHttpSessionAttributes.getString("poolName");

        setPoolName(resolveProperty(poolNamePropertyName(), defaultPoolName));
    }

    private void configureServerRegionShortcut(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        RegionShortcut defaultServerRegionShortcut = enableGemFireHttpSessionAttributes
                .getEnum("serverRegionShortcut");

        setServerRegionShortcut(resolveProperty(serverRegionShortcutPropertyName(), RegionShortcut.class,
                defaultServerRegionShortcut));
    }

    private void configureSessionExpirationPolicyBeanName(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        String defaultSessionExpirationPolicyBeanName = enableGemFireHttpSessionAttributes
                .getString("sessionExpirationPolicyBeanName");

        setSessionExpirationPolicyBeanName(resolveProperty(sessionExpirationPolicyBeanNamePropertyName(),
                defaultSessionExpirationPolicyBeanName));
    }

    private void configureSessionRegionName(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        String defaultSessionRegionName = enableGemFireHttpSessionAttributes.getString("regionName");

        setSessionRegionName(resolveProperty(sessionRegionNamePropertyName(), defaultSessionRegionName));
    }

    private void configureSessionSerializerBeanName(AnnotationAttributes enableGemFireHttpSessionAttributes) {

        String defaultSessionSerializerBeanName = enableGemFireHttpSessionAttributes
                .getString("sessionSerializerBeanName");

        setSessionSerializerBeanName(
                resolveProperty(sessionSerializerBeanNamePropertyName(), defaultSessionSerializerBeanName));
    }

    /**
     * Applies configuration from a single {@link SpringSessionGemFireConfigurer} bean
     * declared in the Spring {@link ApplicationContext}.
     *
     * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SpringSessionGemFireConfigurer
     * @see #resolveSpringSessionGemFireConfigurer()
     */
    void applySpringSessionGemFireConfigurer() {

        resolveSpringSessionGemFireConfigurer().map(this::applyClientRegionShortcut)
                .map(this::applyExposeConfigurationAsProperties).map(this::applyIndexableSessionAttributes)
                .map(this::applyMaxInactiveIntervalInSeconds).map(this::applyPoolName)
                .map(this::applyServerRegionShortcut).map(this::applySessionExpirationPolicyBeanName)
                .map(this::applySessionRegionName).map(this::applySessionSerializerBeanName);
    }

    private Optional<SpringSessionGemFireConfigurer> resolveSpringSessionGemFireConfigurer() {

        try {
            return Optional.of(getApplicationContext().getBean(SpringSessionGemFireConfigurer.class));
        } catch (BeansException cause) {

            if (isCauseBecauseNoSpringSessionGemFireConfigurerPresent(cause)) {
                return Optional.empty();
            }

            throw cause;
        }
    }

    private boolean isCauseBecauseNoSpringSessionGemFireConfigurerPresent(Exception cause) {
        return (!(cause instanceof NoUniqueBeanDefinitionException)
                && cause instanceof NoSuchBeanDefinitionException);
    }

    private <T> SpringSessionGemFireConfigurer applySpringSessionGemFireConfigurerConfiguration(
            @Nullable SpringSessionGemFireConfigurer configurer, @NonNull String methodName,
            @NonNull Function<SpringSessionGemFireConfigurer, T> getter, @NonNull Consumer<T> setter) {

        Optional.ofNullable(configurer).filter(it -> isOverriddenMethodPresent(configurer, methodName)).map(getter)
                .ifPresent(setter);

        return configurer;
    }

    private SpringSessionGemFireConfigurer applyClientRegionShortcut(SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer,
                CONFIGURER_GET_CLIENT_REGION_SHORTCUT_METHOD_NAME,
                SpringSessionGemFireConfigurer::getClientRegionShortcut, this::setClientRegionShortcut);
    }

    private SpringSessionGemFireConfigurer applyExposeConfigurationAsProperties(
            SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer,
                CONFIGURER_GET_EXPOSE_CONFIGURATION_IN_PROPERTIES_METHOD_NAME,
                SpringSessionGemFireConfigurer::getExposeConfigurationAsProperties,
                this::setExposeConfigurationAsProperties);
    }

    private SpringSessionGemFireConfigurer applyIndexableSessionAttributes(
            SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer,
                CONFIGURER_GET_INDEXABLE_SESSION_ATTRIBUTES_METHOD_NAME,
                SpringSessionGemFireConfigurer::getIndexableSessionAttributes, this::setIndexableSessionAttributes);
    }

    private SpringSessionGemFireConfigurer applyMaxInactiveIntervalInSeconds(
            SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer,
                CONFIGURER_GET_MAX_INACTIVE_INTERVAL_IN_SECONDS_METHOD_NAME,
                SpringSessionGemFireConfigurer::getMaxInactiveIntervalInSeconds,
                this::setMaxInactiveIntervalInSeconds);
    }

    private SpringSessionGemFireConfigurer applyPoolName(SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer, CONFIGURER_GET_POOL_NAME_METHOD_NAME,
                SpringSessionGemFireConfigurer::getPoolName, this::setPoolName);
    }

    private SpringSessionGemFireConfigurer applyServerRegionShortcut(SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer,
                CONFIGURER_GET_SERVER_REGION_SHORTCUT_METHOD_NAME,
                SpringSessionGemFireConfigurer::getServerRegionShortcut, this::setServerRegionShortcut);
    }

    private SpringSessionGemFireConfigurer applySessionExpirationPolicyBeanName(
            SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer,
                CONFIGURER_GET_SESSION_EXPIRATION_POLICY_BEAN_NAME_METHOD_NAME,
                SpringSessionGemFireConfigurer::getSessionExpirationPolicyBeanName,
                this::setSessionExpirationPolicyBeanName);
    }

    private SpringSessionGemFireConfigurer applySessionRegionName(SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer, CONFIGURER_GET_REGION_NAME_METHOD_NAME,
                SpringSessionGemFireConfigurer::getRegionName, this::setSessionRegionName);
    }

    private SpringSessionGemFireConfigurer applySessionSerializerBeanName(
            SpringSessionGemFireConfigurer configurer) {

        return applySpringSessionGemFireConfigurerConfiguration(configurer,
                CONFIGURER_GET_SESSION_SERIALIZER_BEAN_NAME_METHOD_NAME,
                SpringSessionGemFireConfigurer::getSessionSerializerBeanName, this::setSessionSerializerBeanName);
    }

    /**
     * Exposes the configuration of Spring Session using either Apache Geode or Pivotal GemFire as {@link Properties}
     * in the Spring {@link Environment}.
     *
     * @see #isExposeConfigurationAsProperties()
     */
    void exposeSpringSessionGemFireConfiguration() {

        if (isExposeConfigurationAsProperties()) {

            Optional.ofNullable(getEnvironment()).filter(ConfigurableEnvironment.class::isInstance)
                    .map(ConfigurableEnvironment.class::cast).map(ConfigurableEnvironment::getPropertySources)
                    .map(propertySources -> {

                        Properties springSessionGemFireProperties = new Properties();

                        PropertySource springSessionGemFirePropertySource = new PropertiesPropertySource(
                                SPRING_SESSION_GEMFIRE_PROPERTY_SOURCE, springSessionGemFireProperties);

                        propertySources.addFirst(springSessionGemFirePropertySource);

                        return springSessionGemFireProperties;
                    }).ifPresent(properties -> {

                        properties.setProperty(clientRegionShortcutPropertyName(),
                                getClientRegionShortcut().name());

                        properties.setProperty(exposeConfigurationAsPropertiesPropertyName(),
                                String.valueOf(isExposeConfigurationAsProperties()));

                        // TODO: deprecate and remove indexableSessionAttributes
                        properties.setProperty(indexableSessionAttributesPropertyName(),
                                StringUtils.arrayToCommaDelimitedString(getIndexableSessionAttributes()));

                        properties.setProperty(indexedSessionAttributesPropertyName(),
                                StringUtils.arrayToCommaDelimitedString(getIndexableSessionAttributes()));

                        properties.setProperty(maxInactiveIntervalInSecondsPropertyName(),
                                String.valueOf(getMaxInactiveIntervalInSeconds()));

                        properties.setProperty(poolNamePropertyName(), getPoolName());

                        properties.setProperty(sessionRegionNamePropertyName(), getSessionRegionName());

                        properties.setProperty(serverRegionShortcutPropertyName(),
                                getServerRegionShortcut().name());

                        getSessionExpirationPolicyBeanName().ifPresent(
                                it -> properties.setProperty(sessionExpirationPolicyBeanNamePropertyName(), it));

                        properties.setProperty(sessionSerializerBeanNamePropertyName(),
                                getSessionSerializerBeanName());

                    });
        }
    }

    @PostConstruct
    public void init() {
        getBeanFactory().registerAlias(getSessionSerializerBeanName(), SESSION_SERIALIZER_BEAN_ALIAS);
    }

    @Bean
    BeanPostProcessor sessionExpirationTimeoutAwareBeanPostProcessor() {

        Duration expirationTimeout = Duration.ofSeconds(getMaxInactiveIntervalInSeconds());

        return new SessionExpirationTimeoutAwareBeanPostProcessor(expirationTimeout);
    }

    @Bean
    BeanPostProcessor sessionSerializerConfigurationBeanPostProcessor() {

        return new BeanPostProcessor() {

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

                if (bean instanceof CacheFactoryBean) {

                    SessionSerializer sessionSerializer = resolveSessionSerializer();

                    configureSerialization((CacheFactoryBean) bean, sessionSerializer);
                }

                return bean;
            }
        };
    }

    private Optional<SessionExpirationPolicy> resolveSessionExpirationPolicy() {

        Optional<String> sessionExpirationPolicyBeanName = getSessionExpirationPolicyBeanName();

        if (sessionExpirationPolicyBeanName.isPresent()) {
            if (getApplicationContext().containsBean(sessionExpirationPolicyBeanName.get())) {
                return Optional.of(getApplicationContext().getBean(sessionExpirationPolicyBeanName.get(),
                        SessionExpirationPolicy.class));
            } else {

                String logMessage = "No Bean with name [{}] and type [{}] was configured;"
                        + " Defaulting to Expiration policy configured for Region [{}]";

                getLogger().warn(logMessage, sessionExpirationPolicyBeanName.get(),
                        SessionExpirationPolicy.class.getName(), getSessionRegionName());
            }
        }

        return Optional.empty();
    }

    private SessionSerializer resolveSessionSerializer() {
        return getApplicationContext().getBean(SESSION_SERIALIZER_BEAN_ALIAS, SessionSerializer.class);
    }

    private boolean isDataSerializerSessionSerializerAdapterPresent() {

        String[] beanNames = getApplicationContext()
                .getBeanNamesForType(DataSerializerSessionSerializerAdapter.class);

        return !ArrayUtils.isEmpty(beanNames);
    }

    @SuppressWarnings("unchecked")
    private void configureSerialization(CacheFactoryBean cacheFactoryBean, SessionSerializer sessionSerializer) {

        if (sessionSerializer instanceof DataSerializer) {

            if (sessionSerializer instanceof DataSerializableSessionSerializer) {
                DataSerializableSessionSerializer.register();
            } else {
                DataSerializer.register(sessionSerializer.getClass());
            }

            setUseDataSerialization(true);
        } else if (sessionSerializer instanceof PdxSerializer) {
            cacheFactoryBean.setPdxSerializer(ComposablePdxSerializer.compose((PdxSerializer) sessionSerializer,
                    cacheFactoryBean.getPdxSerializer()));
        } else {
            Optional.ofNullable(sessionSerializer).filter(it -> !isDataSerializerSessionSerializerAdapterPresent())
                    .ifPresent(serializer -> cacheFactoryBean.setPdxSerializer(ComposablePdxSerializer.compose(
                            new PdxSerializerSessionSerializerAdapter<>(sessionSerializer),
                            cacheFactoryBean.getPdxSerializer())));
        }
    }

    /**
     * {@link SessionSerializer} bean implemented with Apache Geode/Pivotal GemFire DataSerialization framework.
     *
     * @return a DataSerialization {@link SessionSerializer} implementation.
     * @see org.springframework.session.data.gemfire.serialization.data.provider.DataSerializableSessionSerializer
     * @see org.springframework.session.data.gemfire.serialization.SessionSerializer
     */
    @Bean(SESSION_DATA_SERIALIZER_BEAN_NAME)
    public Object sessionDataSerializer() {
        return new DataSerializableSessionSerializer();
    }

    /**
     * {@link SessionSerializer} bean implemented with Apache Geode/Pivotal GemFire PDX serialization framework.
     *
     * @return a PDX serialization {@link SessionSerializer} implementation.
     * @see org.springframework.session.data.gemfire.serialization.pdx.provider.PdxSerializableSessionSerializer
     * @see org.springframework.session.data.gemfire.serialization.SessionSerializer
     */
    @Bean(SESSION_PDX_SERIALIZER_BEAN_NAME)
    public Object sessionPdxSerializer() {
        return new PdxSerializableSessionSerializer();
    }

    /**
     * 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 {@link GemFireCache}.
     * @param sessionRegionAttributes {@link RegionAttributes} used to configure the {@link Region}.
     * @return a {@link SessionCacheTypeAwareRegionFactoryBean} 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 #getPoolName()
     * @see #getServerRegionShortcut()
     * @see #getSessionRegionName()
     */
    @Bean(name = DEFAULT_SESSION_REGION_NAME)
    public SessionCacheTypeAwareRegionFactoryBean<Object, Session> sessionRegion(GemFireCache gemfireCache,
            @Qualifier("sessionRegionAttributes") RegionAttributes<Object, Session> sessionRegionAttributes) {

        SessionCacheTypeAwareRegionFactoryBean<Object, Session> sessionRegion = new SessionCacheTypeAwareRegionFactoryBean<>();

        sessionRegion.setAttributes(sessionRegionAttributes);
        sessionRegion.setCache(gemfireCache);
        sessionRegion.setClientRegionShortcut(getClientRegionShortcut());
        sessionRegion.setPoolName(getPoolName());
        sessionRegion.setRegionName(getSessionRegionName());
        sessionRegion.setServerRegionShortcut(getServerRegionShortcut());

        return sessionRegion;
    }

    /**
     * Defines a {@link RegionAttributes} used to configure and initialize the cache {@link Region}
     * used to store {@link Session} state.
     *
     * 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
     * @see #isExpirationAllowed(GemFireCache)
     */
    @Bean
    @SuppressWarnings({ "unchecked", "deprecation" })
    public RegionAttributesFactoryBean sessionRegionAttributes(GemFireCache gemfireCache) {

        RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean();

        regionAttributes.setKeyConstraint(SESSION_REGION_KEY_CONSTRAINT);
        regionAttributes.setValueConstraint(SESSION_REGION_VALUE_CONSTRAINT);

        if (isExpirationAllowed(gemfireCache)) {

            regionAttributes.setStatisticsEnabled(true);

            regionAttributes.setEntryIdleTimeout(new ExpirationAttributes(
                    Math.max(getMaxInactiveIntervalInSeconds(), 0), ExpirationAction.INVALIDATE));

            resolveSessionExpirationPolicy().map(SessionExpirationPolicyCustomExpiryAdapter::new)
                    .ifPresent(regionAttributes::setCustomEntryIdleTimeout);
        } else {
            getLogger().info("Expiration is not allowed on Regions with a data management policy of {}",
                    GemfireUtils.isClient(gemfireCache) ? getClientRegionShortcut() : getServerRegionShortcut());
        }

        return regionAttributes;
    }

    /**
     * Determines whether expiration configuration is allowed to be set on the cache {@link Region}
     * used to store and manage {@link Session} state.
     *
     * @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())
                : 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()));
    }

    /**
     * Defines the {@link SessionRepository} bean used to interact with Apache Geode or Pivotal GemFire
     * as the Spring Session provider.
     *
     * @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(
            @Qualifier("sessionRegionTemplate") GemfireOperations gemfireOperations) {

        GemFireOperationsSessionRepository sessionRepository = new GemFireOperationsSessionRepository(
                gemfireOperations);

        sessionRepository.setIsDirtyPredicate(getIsDirtyPredicate());
        sessionRepository.setMaxInactiveIntervalInSeconds(getMaxInactiveIntervalInSeconds());
        sessionRepository.setUseDataSerialization(isUsingDataSerialization());

        return sessionRepository;
    }

    /**
     * Defines a Pivotal GemFire Index bean on the Pivotal GemFire cache {@link Region} storing and managing Sessions,
     * specifically on the 'principalName' property for quick lookup of Sessions by 'principalName'.
     *
     * @param gemfireCache a reference to the Pivotal GemFire cache.
     * @return a {@link IndexFactoryBean} to create an Pivotal GemFire Index on the 'principalName' property
     * for Sessions stored in the Pivotal GemFire cache {@link Region}.
     * @see org.springframework.data.gemfire.IndexFactoryBean
     * @see org.apache.geode.cache.GemFireCache
     */
    @Bean
    @DependsOn(DEFAULT_SESSION_REGION_NAME)
    public IndexFactoryBean principalNameIndex(GemFireCache gemfireCache) {

        IndexFactoryBean principalNameIndex = new IndexFactoryBean();

        principalNameIndex.setCache(gemfireCache);
        principalNameIndex.setName("principalNameIndex");
        principalNameIndex.setExpression("principalName");
        principalNameIndex.setFrom(RegionUtils.toRegionPath(getSessionRegionName()));
        principalNameIndex.setOverride(true);
        principalNameIndex.setType(IndexType.HASH);

        return principalNameIndex;
    }

    /**
     * Defines a Pivotal GemFire Index bean on the Pivotal GemFire cache {@link Region} storing and managing Sessions,
     * specifically on all Session attributes for quick lookup and queries on Session attribute names
     * with a given value.
     *
     * @param gemfireCache a reference to the Pivotal GemFire cache.
     * @return a {@link IndexFactoryBean} to create an Pivotal GemFire Index on attributes of Sessions
     * stored in the Pivotal GemFire cache {@link Region}.
     * @see org.springframework.data.gemfire.IndexFactoryBean
     * @see org.apache.geode.cache.GemFireCache
     */
    @Bean
    @DependsOn(DEFAULT_SESSION_REGION_NAME)
    public SessionAttributesIndexFactoryBean sessionAttributesIndex(GemFireCache gemfireCache) {

        SessionAttributesIndexFactoryBean sessionAttributesIndex = new SessionAttributesIndexFactoryBean();

        sessionAttributesIndex.setGemFireCache(gemfireCache);
        sessionAttributesIndex.setIndexableSessionAttributes(getIndexableSessionAttributes());
        sessionAttributesIndex.setRegionName(getSessionRegionName());

        return sessionAttributesIndex;
    }
}