diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/SessionExpirationPolicy.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/SessionExpirationPolicy.java
new file mode 100644
index 0000000..cd98d3c
--- /dev/null
+++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/SessionExpirationPolicy.java
@@ -0,0 +1,78 @@
+/*
+ * 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.expiration;
+
+import java.time.Duration;
+
+import org.springframework.lang.NonNull;
+import org.springframework.session.Session;
+
+/**
+ * The {@link SessionExpirationPolicy} interface is a Strategy interface defining a contract for users to implement
+ * different {@link Session} expiration policies and rules.
+ *
+ * Examples of different {@link Session} expiration strategies might include, but are not limited to:
+ * idle expiration timeout or fixed duration expiration timeouts, and so on.
+ *
+ * @author John Blum
+ * @see java.time.Duration
+ * @see org.springframework.session.Session
+ * @see org.springframework.session.data.gemfire.expiration.support.FixedTimeoutSessionExpirationPolicy
+ * @see org.springframework.session.data.gemfire.expiration.support.IdleTimeoutSessionExpirationPolicy
+ * @since 2.1.0
+ */
+@FunctionalInterface
+@SuppressWarnings("unused")
+public interface SessionExpirationPolicy {
+
+ /**
+ * Defines the {@link Duration length of time} until the given {@link Session} will expire.
+ *
+ * @param session {@link Session} to evaluate.
+ * @return a {@link Duration} specifying the length of time until the {@link Session} will expire.
+ * @see org.springframework.session.Session
+ * @see java.time.Duration
+ */
+ @NonNull
+ Duration expireAfter(Session session);
+
+ /**
+ * Defines the {@link ExpirationAction action} to take when the {@link Session} expires.
+ *
+ * Defaults to {@link ExpirationAction#INVALIDATE}.
+ *
+ * @return an {@link ExpirationAction} specifying the action to take when the {@link Session} expires.
+ * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy.ExpirationAction
+ */
+ default ExpirationAction getAction() {
+ return ExpirationAction.INVALIDATE;
+ }
+
+ /**
+ * Enumeration of different actions to take when the {@link Session} expires.
+ */
+ enum ExpirationAction {
+
+ DESTROY,
+ INVALIDATE;
+
+ public static ExpirationAction defaultIfNull(ExpirationAction expirationAction) {
+ return expirationAction != null ? expirationAction : INVALIDATE;
+ }
+
+ }
+}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/FixedDurationExpirationSessionRepositoryBeanPostProcessor.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/FixedDurationExpirationSessionRepositoryBeanPostProcessor.java
new file mode 100644
index 0000000..5d1190f
--- /dev/null
+++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/FixedDurationExpirationSessionRepositoryBeanPostProcessor.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.expiration.config;
+
+import java.time.Duration;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.lang.Nullable;
+import org.springframework.session.Session;
+import org.springframework.session.SessionRepository;
+import org.springframework.session.data.gemfire.expiration.repository.FixedDurationExpirationSessionRepository;
+
+/**
+ * The {@link FixedDurationExpirationSessionRepositoryBeanPostProcessor} class wraps an existing, data store specific,
+ * instance of {@link SessionRepository} in an instance of {@link FixedDurationExpirationSessionRepository} initialized
+ * with a provided {@link Duration} for the expiration timeout to implement lazy, fixed {@link Duration} expiration
+ * on all {@link Session Sessions}.
+ *
+ * @author John Blum
+ * @see org.springframework.beans.factory.config.BeanPostProcessor
+ * @see org.springframework.session.Session
+ * @see org.springframework.session.SessionRepository
+ * @see org.springframework.session.data.gemfire.expiration.repository.FixedDurationExpirationSessionRepository
+ * @see Absolute Session Timeouts
+ * @since 2.1.0
+ */
+@SuppressWarnings("unused")
+public class FixedDurationExpirationSessionRepositoryBeanPostProcessor implements BeanPostProcessor {
+
+ private final Duration expirationTimeout;
+
+ /**
+ * Constructs a new instance of {@link FixedDurationExpirationSessionRepositoryBeanPostProcessor} initialized with
+ * the given {@link Duration} to implement lazy, fixed {@link Duration} expiration policy
+ * on all {@link Session Sessions}.
+ *
+ * @param expirationTimeout {@link Duration} indicating the length of time until the {@link Session} expires.
+ * @see java.time.Duration
+ */
+ public FixedDurationExpirationSessionRepositoryBeanPostProcessor(@Nullable Duration expirationTimeout) {
+ this.expirationTimeout = expirationTimeout;
+ }
+
+ /**
+ * Returns the configured {@link Session} {@link Duration expiration timeout}.
+ *
+ * @return the configured {@link Session} {@link Duration expiration timeout}.
+ * @see java.time.Duration
+ */
+ protected Duration getExpirationTimeout() {
+ return this.expirationTimeout;
+ }
+
+ @Nullable @Override @SuppressWarnings("unchecked")
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+
+ return bean instanceof SessionRepository
+ ? new FixedDurationExpirationSessionRepository<>((SessionRepository) bean, getExpirationTimeout())
+ : bean;
+ }
+}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/SessionExpirationTimeoutAware.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/SessionExpirationTimeoutAware.java
new file mode 100644
index 0000000..3c43ee9
--- /dev/null
+++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/SessionExpirationTimeoutAware.java
@@ -0,0 +1,47 @@
+/*
+ * 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.expiration.config;
+
+import java.time.Duration;
+
+import org.springframework.session.Session;
+import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
+
+/**
+ * The {@link SessionExpirationTimeoutAware} interface is a configuration callback interface allowing implementors
+ * to receive a callback with the configured {@link Session} {@link Duration expiration timeout} as set on the
+ * {@link EnableGemFireHttpSession} annotation, {@link EnableGemFireHttpSession#maxInactiveIntervalInSeconds()}
+ * attribute.
+ *
+ * @author John Blum
+ * @see java.time.Duration
+ * @see org.springframework.session.Session
+ * @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
+ * @since 2.1.0
+ */
+@SuppressWarnings("unused")
+public interface SessionExpirationTimeoutAware {
+
+ /**
+ * Configures the {@link Session} {@link Duration expiration timeout} on this implementating object.
+ *
+ * @param expirationTimeout {@link Duration} specifying the expiration timeout fo the {@link Session}.
+ * @see java.time.Duration
+ */
+ void setExpirationTimeout(Duration expirationTimeout);
+
+}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/SessionExpirationTimeoutAwareBeanPostProcessor.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/SessionExpirationTimeoutAwareBeanPostProcessor.java
new file mode 100644
index 0000000..ef7c09b
--- /dev/null
+++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/config/SessionExpirationTimeoutAwareBeanPostProcessor.java
@@ -0,0 +1,74 @@
+/*
+ * 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.expiration.config;
+
+import java.time.Duration;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.lang.Nullable;
+import org.springframework.session.Session;
+import org.springframework.util.Assert;
+
+/**
+ * The {@link SessionExpirationTimeoutAwareBeanPostProcessor} class is a Spring {@link BeanPostProcessor} handling
+ * the post processing of all Spring beans defined in the Spring container implementing
+ * the {@link SessionExpirationTimeoutAware} interface.
+ *
+ * @author John Blum
+ * @see org.springframework.beans.factory.config.BeanPostProcessor
+ * @since 2.1.0
+ */
+public class SessionExpirationTimeoutAwareBeanPostProcessor implements BeanPostProcessor {
+
+ private final Duration expirationTimeout;
+
+ /**
+ * Constructs a new {@link SessionExpirationTimeoutAwareBeanPostProcessor} initialized with
+ * the given {@link Session} {@link Duration expiration timeout}.
+ *
+ * @param expirationTimeout {@link Duration} specifying the length of time until {@link Session} expires.
+ * @throws IllegalArgumentException if {@link Duration} is {@literal null}.
+ * @see java.time.Duration
+ */
+ public SessionExpirationTimeoutAwareBeanPostProcessor(Duration expirationTimeout) {
+
+ Assert.notNull(expirationTimeout, "Expiration timeout is required");
+
+ this.expirationTimeout = expirationTimeout;
+ }
+
+ /**
+ * Returns the configured {@link Session} {@link Duration expiration timeout}.
+ *
+ * @return the configured {@link Session} {@link Duration expiration timeout}.
+ * @see java.time.Duration
+ */
+ protected Duration getExpirationTimeout() {
+ return this.expirationTimeout;
+ }
+
+ @Nullable @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+
+ if (bean instanceof SessionExpirationTimeoutAware) {
+ ((SessionExpirationTimeoutAware) bean).setExpirationTimeout(getExpirationTimeout());
+ }
+
+ return bean;
+ }
+}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/repository/FixedDurationExpirationSessionRepository.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/repository/FixedDurationExpirationSessionRepository.java
new file mode 100644
index 0000000..258560a
--- /dev/null
+++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/repository/FixedDurationExpirationSessionRepository.java
@@ -0,0 +1,199 @@
+/*
+ * 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.expiration.repository;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Optional;
+
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.session.Session;
+import org.springframework.session.SessionRepository;
+
+import org.apache.shiro.util.Assert;
+
+/**
+ * The {@link FixedDurationExpirationSessionRepository} class is a {@link SessionRepository} implementation wrapping
+ * an existing {@link SessionRepository}, data store specific, implementation in order to implement
+ * a fixed {@link Duration} expiration policy on the {@link Session}.
+ *
+ * That is, the {@link Session} will always expire (or be considered "expired") after a fixed amount of time. Even if
+ * the user {@link Session} is still actively being accessed up to the last moment right before the {@link Session}
+ * is about to expire, the {@link Session} will expire regardless.
+ *
+ * This may be useful in certain UCs where, for security reasons, the {@link Session} must expire no matter what.
+ *
+ * @author John Blum
+ * @see java.time.Duration
+ * @see java.time.Instant
+ * @see org.springframework.session.Session
+ * @see org.springframework.session.SessionRepository
+ * @see Absolute Session Timeouts
+ * @since 2.1.0
+ */
+@SuppressWarnings("unused")
+public class FixedDurationExpirationSessionRepository implements SessionRepository {
+
+ private final SessionRepository delegate;
+
+ private final Duration expirationTimeout;
+
+ /**
+ * Constructs a new instance of {@link FixedDurationExpirationSessionRepository} initialized with the given
+ * data store specific {@link SessionRepository}.
+ *
+ * @param sessionRepository {@link SessionRepository} delegate.
+ * @param expirationTimeout {@link Duration} specifying the length of time until the {@link Session} expires.
+ * @throws IllegalArgumentException if {@link SessionRepository} is {@literal null}.
+ * @see org.springframework.session.SessionRepository
+ * @see java.time.Duration
+ */
+ public FixedDurationExpirationSessionRepository(@NonNull SessionRepository sessionRepository,
+ @Nullable Duration expirationTimeout) {
+
+ Assert.notNull(sessionRepository, "SessionRepository is required");
+
+ this.delegate = sessionRepository;
+ this.expirationTimeout = expirationTimeout;
+ }
+
+ /**
+ * Returns a reference to the data store specific {@link SessionRepository}.
+ *
+ * @return a reference to the data store specific {@link SessionRepository}.
+ * @see org.springframework.session.SessionRepository
+ */
+ @NonNull
+ protected SessionRepository getDelegate() {
+ return this.delegate;
+ }
+
+ /**
+ * Return an {@link Optional} {@link Duration expiraiton timeout}.
+ *
+ * @return an {@link Optional} {@link Duration expiraiton timeout}.
+ * @see java.time.Duration
+ * @see java.util.Optional
+ */
+ public Optional getExpirationTimeout() {
+ return Optional.ofNullable(this.expirationTimeout);
+ }
+
+ /**
+ * Creates a new instance of {@link Session}.
+ *
+ * The {@link Session} instance will be managed by Apache Geode or Pivotal GemFire.
+ *
+ * @return a new instance of {@link Session}.
+ * @see org.springframework.session.Session
+ */
+ @Override
+ public S createSession() {
+ return getDelegate().createSession();
+ }
+
+ /**
+ * Finds a {@link Session} with the given {@link String ID}.
+ *
+ * This method will also perform a lazy expiration check to determine if the {@link Session} has already expired
+ * upon access, and if so, delete the {@link Session} with the given {@link String ID}.
+ *
+ * @param id {@link String} containing the ID identifying the {@link Session} to lookup.
+ * @return the {@link Session} with the given {@link String ID} or {@literal null} if no {@link Session}
+ * with {@link String ID} exists or the {@link Session} is expired.
+ * @see org.springframework.session.Session
+ * @see #handleExpired(Session, Duration)
+ * @see #getExpirationTimeout()
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public S findById(String id) {
+
+ return Optional.ofNullable(getDelegate().findById(id))
+ .map(session -> getExpirationTimeout()
+ .map(expirationDuration -> handleExpired(session, expirationDuration))
+ .orElseGet(()-> getExpirationTimeout().isPresent() ? null : session)
+ ).orElse(null);
+ }
+
+ /**
+ * Handles the expiration event for the given {@link Session} if the {@link Session} has expired.
+ *
+ * @param session {@link Session} to evaluate for expiration.
+ * @param expirationDuration {@link Duration} indicating the length of time before an idle,
+ * unused or old {@link Session} expires.
+ * @return the given {@link Session} or {@literal null} if the {@link Session} has already expired.
+ * @see #isExpired(Session, Duration)
+ * @see #deleteById(String)
+ * @see org.springframework.session.Session
+ * @see java.time.Duration
+ */
+ S handleExpired(S session, Duration expirationDuration) {
+
+ if (isExpired(session, expirationDuration)) {
+ deleteById(session.getId());
+ session = null;
+ }
+
+ return session;
+ }
+
+ /**
+ * Determines whether the given {@link Session} has expired.
+ *
+ * This {@link SessionRepository} implements fixed duration expiration, which means the {@link Session} will expire
+ * after a fixed length of time (i.e. a fixed {@link Duration}). Even if the user {@link Session} is active
+ * (i.e. not idle), the {@link Session} will still expire after the fixed {@link Duration} is exceeded.
+ *
+ * @param session {@link Session} to evaluate.
+ * @param expirationDuration {@link Duration} indicating the length of time before the {@link Session} expires.
+ * @return a boolean value indication whether the {@link Session} has expired.
+ * @see org.springframework.session.Session
+ * @see java.time.Duration
+ * @see java.time.Instant
+ */
+ boolean isExpired(S session, Duration expirationDuration) {
+
+ Instant sessionCreationTime = session.getCreationTime();
+ Instant now = Instant.now();
+
+ return now.minusMillis(expirationDuration.toMillis()).isAfter(sessionCreationTime);
+ }
+
+ /**
+ * Saves the given {@link Session} to the underlying data (persistent) store.
+ *
+ * @param session {@link Session} to save.
+ * @see org.springframework.session.Session
+ */
+ @Override
+ public void save(S session) {
+ getDelegate().save(session);
+ }
+
+ /**
+ * Deletes the {@link Session} identified by the given {@link String ID}.
+ *
+ * @param id {@link String} containing the ID of the {@link Session} to delete.
+ * @see org.springframework.session.Session
+ */
+ @Override
+ public void deleteById(String id) {
+ getDelegate().deleteById(id);
+ }
+}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/FixedTimeoutSessionExpirationPolicy.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/FixedTimeoutSessionExpirationPolicy.java
new file mode 100644
index 0000000..73448d6
--- /dev/null
+++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/FixedTimeoutSessionExpirationPolicy.java
@@ -0,0 +1,79 @@
+/*
+ * 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.expiration.support;
+
+import java.time.Duration;
+
+import org.springframework.lang.NonNull;
+import org.springframework.session.Session;
+import org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy;
+import org.springframework.util.Assert;
+
+/**
+ * An implementation of the {@link SessionExpirationPolicy} interface that specifies an expiration policy based on
+ * a fixed period of time. That is, the {@link Session} will timeout after a fixed {@link Duration} even if the
+ * {@link Session} is still active.
+ *
+ * @author John Blum
+ * @see java.time.Duration
+ * @see org.springframework.session.Session
+ * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy
+ * @since 2.1.0
+ */
+@SuppressWarnings("unused")
+public class FixedTimeoutSessionExpirationPolicy implements SessionExpirationPolicy {
+
+ private final Duration fixedExpirationTimeout;
+
+ /**
+ * Constructs a new {@link FixedTimeoutSessionExpirationPolicy} initialized with the given
+ * {@link Duration fixed expiration timeout}.
+ *
+ * @param fixedExpirationTimeout {@link Duration} specifying the fixed length of time until
+ * the {@link Session} expires.
+ * @throws IllegalArgumentException if {@link Duration} is {@literal null}.
+ * @see java.time.Duration
+ */
+ public FixedTimeoutSessionExpirationPolicy(@NonNull Duration fixedExpirationTimeout) {
+
+ Assert.notNull(fixedExpirationTimeout, "Fixed expiration timeout is required");
+
+ this.fixedExpirationTimeout = fixedExpirationTimeout;
+ }
+
+ /**
+ * Return the configured {@link Duration fixed expiration timeout}.
+ *
+ * @return the configured {@link Duration fixed expiration timeout}.
+ * @see java.time.Duration
+ */
+ protected Duration getFixedExpirationTimeout() {
+ return this.fixedExpirationTimeout;
+ }
+
+ @NonNull @Override
+ public Duration expireAfter(@NonNull Session session) {
+
+ long currentTimeMinusCreationTime =
+ Math.max(System.currentTimeMillis() - session.getCreationTime().toEpochMilli(), 0);
+
+ Duration expirationDuration =
+ getFixedExpirationTimeout().minus(Duration.ofMillis(currentTimeMinusCreationTime));
+
+ return expirationDuration.isNegative() ? Duration.ZERO : expirationDuration;
+ }
+}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/IdleTimeoutSessionExpirationPolicy.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/IdleTimeoutSessionExpirationPolicy.java
new file mode 100644
index 0000000..a1a363d
--- /dev/null
+++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/IdleTimeoutSessionExpirationPolicy.java
@@ -0,0 +1,78 @@
+/*
+ * 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.expiration.support;
+
+import java.time.Duration;
+
+import org.springframework.lang.NonNull;
+import org.springframework.session.Session;
+import org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy;
+import org.springframework.util.Assert;
+
+/**
+ * An implementation of the {@link SessionExpirationPolicy} interface that specifies an expiration policy
+ * based on inactive, idle {@link Session Sessions} exceeding a predefined time period for expiration.
+ *
+ * @author John Blum
+ * @see java.time.Duration
+ * @see org.springframework.session.Session
+ * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy
+ * @since 2.1.0
+ */
+@SuppressWarnings("unused")
+public class IdleTimeoutSessionExpirationPolicy implements SessionExpirationPolicy {
+
+ private final Duration idleExpirationTimeout;
+
+ /**
+ * Constructs a new instance of {@link IdleTimeoutSessionExpirationPolicy} initialized with
+ * the given {@link Duration expiration timeout}.
+ *
+ * @param idleExpirationTimeout {@link Duration} specifying the length of time until the {@link Session} expires.
+ * @throws IllegalArgumentException if {@link Duration} is {@literal null}.
+ * @see java.time.Duration
+ */
+ public IdleTimeoutSessionExpirationPolicy(@NonNull Duration idleExpirationTimeout) {
+
+ Assert.notNull(idleExpirationTimeout, "Idle expiration timeout is required");
+
+ this.idleExpirationTimeout = idleExpirationTimeout;
+
+ }
+
+ /**
+ * Return the configured {@link Duration idle expiration timeout}.
+ *
+ * @return the configured {@link Duration idle expiration timeout}.
+ * @see java.time.Duration
+ */
+ protected Duration getIdleExpirationTimeout() {
+ return this.idleExpirationTimeout;
+ }
+
+ @NonNull @Override
+ public Duration expireAfter(@NonNull Session session) {
+
+ long currentTimeMinusLastAccessTime =
+ Math.max(System.currentTimeMillis() - session.getLastAccessedTime().toEpochMilli(), 0);
+
+ Duration expirationDuration =
+ getIdleExpirationTimeout().minus(Duration.ofMillis(currentTimeMinusLastAccessTime));
+
+ return expirationDuration.isNegative() ? Duration.ZERO : expirationDuration;
+ }
+}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/SessionExpirationPolicyCustomExpiryAdapter.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/SessionExpirationPolicyCustomExpiryAdapter.java
new file mode 100644
index 0000000..e0939c9
--- /dev/null
+++ b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/expiration/support/SessionExpirationPolicyCustomExpiryAdapter.java
@@ -0,0 +1,199 @@
+/*
+ * 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.expiration.support;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import org.apache.geode.cache.CustomExpiry;
+import org.apache.geode.cache.ExpirationAction;
+import org.apache.geode.cache.ExpirationAttributes;
+import org.apache.geode.cache.Region;
+import org.apache.geode.pdx.PdxInstance;
+
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.session.Session;
+import org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy;
+import org.springframework.util.Assert;
+
+/**
+ * The {@link SessionExpirationPolicyCustomExpiryAdapter} class is an Apache Geode/Pivotal GemFire {@link CustomExpiry}
+ * implementation wrapping and adapting an instance of the {@link SessionExpirationPolicy} strategy interface
+ * to plugin to GemFire/Geode's expiration logistics.
+ *
+ * @author John Blum
+ * @see org.apache.geode.cache.CustomExpiry
+ * @see org.apache.geode.cache.ExpirationAction
+ * @see org.apache.geode.cache.ExpirationAttributes
+ * @see org.apache.geode.cache.Region
+ * @see org.springframework.session.Session
+ * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy
+ * @since 1.0.0
+ */
+@SuppressWarnings("unused")
+public class SessionExpirationPolicyCustomExpiryAdapter implements CustomExpiry {
+
+ private final SessionExpirationPolicy sessionExpirationPolicy;
+
+ /**
+ * Constructs a new instance of {@link SessionExpirationPolicyCustomExpiryAdapter} initialized with
+ * the given, required {@link SessionExpirationPolicy}.
+ *
+ * @param sessionExpirationPolicy {@link SessionExpirationPolicy} used to enforce the expiration policy
+ * on all {@link Session Sessions}.
+ * @throws IllegalArgumentException if the {@link SessionExpirationPolicy} is {@literal null}.
+ * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy
+ */
+ public SessionExpirationPolicyCustomExpiryAdapter(@NonNull SessionExpirationPolicy sessionExpirationPolicy) {
+
+ Assert.notNull(sessionExpirationPolicy, "SessionExpirationPolicy is required");
+
+ this.sessionExpirationPolicy = sessionExpirationPolicy;
+ }
+
+ /**
+ * Returns a reference to the {@link SessionExpirationPolicy} defining the expiration policies
+ * for all managed {@link Session Sessions}.
+ *
+ * @return a reference to the {@link SessionExpirationPolicy}.
+ * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy
+ */
+ protected SessionExpirationPolicy getSessionExpirationPolicy() {
+ return this.sessionExpirationPolicy;
+ }
+
+ @Nullable @Override
+ public ExpirationAttributes getExpiry(@Nullable Region.Entry regionEntry) {
+
+ return Optional.ofNullable(resolveSession(regionEntry))
+ .map(this::newExpirationAttributes)
+ .orElse(null);
+ }
+
+ /**
+ * Constructs {@link ExpirationAttributes} from the given {@link Session}.
+ *
+ * @param session {@link Session} used to construct the {@link ExpirationAttributes}.
+ * @return a new {@link ExpirationAttributes} constructed from the given {@link Session}.
+ * @see #newExpirationAttributes(Duration, SessionExpirationPolicy.ExpirationAction)
+ * @see org.apache.geode.cache.ExpirationAttributes
+ * @see org.springframework.session.Session
+ */
+ private ExpirationAttributes newExpirationAttributes(Session session) {
+
+ SessionExpirationPolicy sessionExpirationPolicy = getSessionExpirationPolicy();
+ Duration sessionExpirationDuration = sessionExpirationPolicy.expireAfter(session);
+ SessionExpirationPolicy.ExpirationAction sessionExpirationAction = sessionExpirationPolicy.getAction();
+
+ return newExpirationAttributes(sessionExpirationDuration, sessionExpirationAction);
+ }
+
+ /**
+ * Constructs a new {@link ExpirationAttributes} initialized with the given {@link Duration expiration timeout}
+ * and {@link SessionExpirationPolicy.ExpirationAction} to take when the {@link Session} expires.
+ *
+ * @param duration {@link Duration} specifying the expiration timeout.
+ * @param expirationAction {@link SessionExpirationPolicy.ExpirationAction} to take when
+ * the {@link Session} expires.
+ * @return the new {@link ExpirationAttributes}.
+ * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy.ExpirationAction
+ * @see #newExpirationAttributes(int, ExpirationAction)
+ * @see org.apache.geode.cache.ExpirationAttributes
+ * @see java.time.Duration
+ */
+ private ExpirationAttributes newExpirationAttributes(Duration duration,
+ SessionExpirationPolicy.ExpirationAction expirationAction) {
+
+ int expirationTimeout = (int) Math.min(Integer.MAX_VALUE, Math.max(duration.getSeconds(), 1));
+
+ return newExpirationAttributes(expirationTimeout, resolveExpirationAction(expirationAction));
+ }
+
+ /**
+ * Constructs new {@link ExpirationAttributes} with the given {@link Integer expiration timeout}
+ * and {@link ExpirationAction} to take when the {@link Session} expires.
+ *
+ * @param expirationTimeInSeconds length of time in seconds until the {@link Session} expires.
+ * @param expirationAction {@link ExpirationAction} to take when the {@link Session} expires.
+ * @return the new {@link ExpirationAttributes}.
+ * @see org.apache.geode.cache.ExpirationAttributes
+ */
+ private ExpirationAttributes newExpirationAttributes(int expirationTimeInSeconds,
+ ExpirationAction expirationAction) {
+
+ return new ExpirationAttributes(expirationTimeInSeconds, expirationAction);
+ }
+
+ /**
+ * Resolves the {@link org.apache.geode.cache.ExpirationAction} from the given
+ * {@link SessionExpirationPolicy.ExpirationAction}.
+ *
+ * Defaults to {@link ExpirationAction#INVALIDATE} if {@link SessionExpirationPolicy.ExpirationAction}
+ * is {@literal null}.
+ *
+ * @param expirationAction {@link SessionExpirationPolicy.ExpirationAction} to convert into a
+ * {@link org.apache.geode.cache.ExpirationAction}.
+ * @return an {@link org.apache.geode.cache.ExpirationAction} from the given
+ * {@link SessionExpirationPolicy.ExpirationAction}.
+ * @see org.springframework.session.data.gemfire.expiration.SessionExpirationPolicy.ExpirationAction
+ * @see org.apache.geode.cache.ExpirationAction
+ */
+ private ExpirationAction resolveExpirationAction(SessionExpirationPolicy.ExpirationAction expirationAction) {
+
+ switch (SessionExpirationPolicy.ExpirationAction.defaultIfNull(expirationAction)) {
+ case DESTROY:
+ return ExpirationAction.DESTROY;
+ default:
+ return ExpirationAction.INVALIDATE;
+ }
+ }
+
+ /**
+ * Resolves a {@link Session} object from the given {@link Region.Entry#getValue() Region Entry Value}.
+ *
+ * @param regionEntry {@link Region.Entry} from which to extract the {@link Session} value.
+ * @return a {@link Session} object from the given {@link Region.Entry#getValue()}.
+ * @see org.springframework.session.Session
+ * @see org.apache.geode.cache.Region.Entry
+ * @see #resolveSession(Object)
+ */
+ @Nullable
+ private Session resolveSession(Region.Entry regionEntry) {
+ return resolveSession(Optional.ofNullable(regionEntry).map(Region.Entry::getValue).orElse(null));
+ }
+
+ /**
+ * Resolves a {@link Session} object from the given {@link Object} value.
+ *
+ * The {@link Object} may already be a {@link Session} or may possibly be a {@link PdxInstance} if GemFire/Geode
+ * PDX serialization is enabled.
+ *
+ * @param regionEntryValue {@link Object} to evaluate as a {@link Session}.
+ * @return a {@link Session} from the given {@link Object}.
+ * @see org.springframework.session.Session
+ * @see org.apache.geode.pdx.PdxInstance
+ * @see java.lang.Object
+ */
+ @Nullable
+ private Session resolveSession(Object regionEntryValue) {
+
+ return regionEntryValue instanceof Session ? (Session) regionEntryValue
+ : regionEntryValue instanceof PdxInstance ? (Session) ((PdxInstance) regionEntryValue).getObject()
+ : null;
+ }
+}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/FixedDurationExpirationSessionRepository.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/FixedDurationExpirationSessionRepository.java
deleted file mode 100644
index 55c3e21..0000000
--- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/FixedDurationExpirationSessionRepository.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.Optional;
-
-import org.springframework.session.Session;
-import org.springframework.session.SessionRepository;
-
-/**
- * The {@link FixedDurationExpirationSessionRepository} class...
- *
- * @author John Blum
- * @see org.springframework.session.Session
- * @see org.springframework.session.SessionRepository
- * @see Absolute Session Timeouts
- * @since 2.0.0
- */
-@SuppressWarnings("unused")
-public class FixedDurationExpirationSessionRepository implements SessionRepository {
-
- private final SessionRepository delegate;
-
- private final Duration expirationDuration;
-
- public FixedDurationExpirationSessionRepository(SessionRepository sessionRepository, Duration expirationDuration) {
-
- this.delegate = Optional.ofNullable(sessionRepository)
- .orElseThrow(() -> new IllegalArgumentException("SessionRepository is required"));
-
- this.expirationDuration = expirationDuration;
- }
-
- protected SessionRepository getDelegate() {
- return this.delegate;
- }
-
- public Optional getExpirationDuration() {
- return Optional.ofNullable(this.expirationDuration);
- }
-
- @Override
- public S createSession() {
- return getDelegate().createSession();
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public S findById(String id) {
-
- return Optional.ofNullable(getDelegate().findById(id))
- .map(session ->
- getExpirationDuration()
- .map(expirationDuration -> handleExpired(session, expirationDuration))
- .orElse(session)
- ).orElse(null);
- }
-
- private S handleExpired(S session, Duration expirationDuration) {
-
- if (isExpired(session, expirationDuration)) {
- deleteById(session.getId());
- session = null;
- }
-
- return session;
- }
-
- private boolean isExpired(S session, Duration expirationDuration) {
-
- Instant sessionCreationTime = session.getCreationTime();
- Instant now = Instant.now();
-
- return now.minusMillis(expirationDuration.toMillis()).isAfter(sessionCreationTime);
- }
-
- @Override
- public void save(S session) {
- getDelegate().save(session);
- }
-
- @Override
- public void deleteById(String id) {
- getDelegate().deleteById(id);
- }
-}
diff --git a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/FixedDurationExpirationSessionRepositoryBeanPostProcessor.java b/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/FixedDurationExpirationSessionRepositoryBeanPostProcessor.java
deleted file mode 100644
index 90bfbe1..0000000
--- a/spring-session-data-geode/src/main/java/org/springframework/session/data/gemfire/support/FixedDurationExpirationSessionRepositoryBeanPostProcessor.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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 org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.lang.Nullable;
-import org.springframework.session.SessionRepository;
-
-/**
- * The {@link FixedDurationExpirationSessionRepositoryBeanPostProcessor} class...
- *
- * @author John Blum
- * @see org.springframework.beans.factory.config.BeanPostProcessor
- * @see org.springframework.session.SessionRepository
- * @see Absolute Session Timeouts
- * @since 2.0.0
- */
-@SuppressWarnings("unused")
-public class FixedDurationExpirationSessionRepositoryBeanPostProcessor implements BeanPostProcessor {
-
- private final Duration expirationDuration;
-
- public FixedDurationExpirationSessionRepositoryBeanPostProcessor(Duration expirationDuration) {
- this.expirationDuration = expirationDuration;
- }
-
- @Nullable @Override @SuppressWarnings("unchecked")
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
-
- return (bean instanceof SessionRepository
- ? new FixedDurationExpirationSessionRepository<>((SessionRepository) bean, this.expirationDuration)
- : bean);
-
- }
-}