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); - - } -}