From f2443f5e21b64c193cd2c1e4c4d27d1f9f7b20ce Mon Sep 17 00:00:00 2001
From: Mark Anderson
Date: Mon, 1 Feb 2016 19:10:09 +0000
Subject: [PATCH] Support External Hazelcast instance
Previously the Hazelcast support only worked with
embedded Hazelcast instances.
This commit ensures that Hazelcast support works
with external Hazelcast instances.
Fixes gh-339
---
.../asciidoc/guides/hazelcast-spring.adoc | 14 +--
docs/src/docs/asciidoc/index.adoc | 7 +-
.../src/main/java/sample/Config.java | 2 +-
...EnableHazelcastHttpSessionEventsTests.java | 25 +++-
...lcastHttpSessionConfigurationXmlTests.java | 79 +------------
.../web/http/EnableHazelcastHttpSession.java | 5 +-
.../HazelcastHttpSessionConfiguration.java | 107 ++++++++++++------
7 files changed, 107 insertions(+), 132 deletions(-)
diff --git a/docs/src/docs/asciidoc/guides/hazelcast-spring.adoc b/docs/src/docs/asciidoc/guides/hazelcast-spring.adoc
index a78d055..95e304e 100644
--- a/docs/src/docs/asciidoc/guides/hazelcast-spring.adoc
+++ b/docs/src/docs/asciidoc/guides/hazelcast-spring.adoc
@@ -81,7 +81,7 @@ The filter is what is in charge of replacing the `HttpSession` implementation to
In this instance Spring Session is backed by Hazelcast.
<2> We create a `HazelcastInstance` that connects Spring Session to Hazelcast.
By default, an embedded instance of Hazelcast is started and connected to by the application.
-For more information on configuring Hazelcast, refer to the http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#hazelcast-configuration[reference documentation].
+For more information on configuring Hazelcast, refer to the http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-configuration[reference documentation].
== Servlet Container Initialization
@@ -126,7 +126,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
====
Hazelcast will run in embedded mode with your application by default, but if you want to connect
to a stand alone instance instead, you can configure it by following the instructions in the
-http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#hazelcast-configuration[reference documentation].
+http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-configuration[reference documentation].
====
----
@@ -157,9 +157,9 @@ Go ahead and view the cookies (click for help with https://developer.chrome.com/
=== Interact with the data store
-If you like, you can remove the session using http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#hazelcast-java-client[a Java client],
-http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#other-client-implementations[one of the other clients], or the
-http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#management-center[management center].
+If you like, you can remove the session using http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-java-client[a Java client],
+http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#other-client-implementations[one of the other clients], or the
+http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#management-center[management center].
==== Using the console
@@ -168,7 +168,7 @@ For example, using the management center console after connecting to your Hazelc
default> ns spring:session:sessions
spring:session:sessions> m.clear
-TIP: The Hazelcast documentation has instructions for http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#console[the console].
+TIP: The Hazelcast documentation has instructions for http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#executing-console-commands[the console].
Alternatively, you can also delete the explicit key. Enter the following into the console ensuring to replace `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie:
@@ -179,7 +179,7 @@ Now visit the application at http://localhost:8080/ and observe that we are no l
==== Using the REST API
As described in the other clients section of the documentation, there is a
-http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#rest-client[REST API]
+http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#rest-client[REST API]
provided by the Hazelcast node(s).
For example, you could delete an individual key as follows (replacing `7e8383a4-082c-4ffe-a4bc-c40fd3363c5e` with the value of your SESSION cookie):
diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc
index 2b27542..25e9efc 100644
--- a/docs/src/docs/asciidoc/index.adoc
+++ b/docs/src/docs/asciidoc/index.adoc
@@ -513,7 +513,7 @@ include::{docs-test-dir}docs/http/HazelcastHttpSessionConfig.java[tags=config]
----
This will configure Hazelcast in embedded mode with default configuration.
-See the http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#hazelcast-configuration[Hazelcast documentation] for
+See the http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-configuration[Hazelcast documentation] for
detailed information on configuration options for Hazelcast.
[[api-enablehazelcasthttpsession-storage]]
@@ -521,8 +521,9 @@ detailed information on configuration options for Hazelcast.
Sessions will be stored in a distributed `Map` in Hazelcast using a <>.
The `Map` interface methods will be used to `get()` and `put()` Sessions.
-The expiration of a session in the `Map` is handled by Hazelcast's native support for configuring a map's `max-idle-seconds` setting.
-Entries (sessions) that have been idle longer than the max idle setting will be automatically removed from the `Map`.
+The expiration of a session in the `Map` is handled by Hazelcast's support for setting the time to live on an entry when it is `put()` into the `Map`. Entries (sessions) that have been idle longer than the time to live will be automatically removed from the `Map`.
+
+You shouldn't need to configure any settings such as `max-idle-seconds` or `time-to-live-seconds` for the `Map` within the Hazelcast configuration.
[[api-enablehazelcasthttpsession-customize]]
==== Basic Customization
diff --git a/samples/hazelcast-spring/src/main/java/sample/Config.java b/samples/hazelcast-spring/src/main/java/sample/Config.java
index 343359e..4fcbcf1 100644
--- a/samples/hazelcast-spring/src/main/java/sample/Config.java
+++ b/samples/hazelcast-spring/src/main/java/sample/Config.java
@@ -26,7 +26,7 @@ import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
// tag::class[]
-@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = "300")
+@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = 300)
@Configuration
public class Config {
diff --git a/spring-session/src/integration-test/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSessionEventsTests.java b/spring-session/src/integration-test/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSessionEventsTests.java
index facde1c..aa88bb1 100644
--- a/spring-session/src/integration-test/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSessionEventsTests.java
+++ b/spring-session/src/integration-test/java/org/springframework/session/hazelcast/config/annotation/web/http/EnableHazelcastHttpSessionEventsTests.java
@@ -59,8 +59,7 @@ import com.hazelcast.core.HazelcastInstance;
@WebAppConfiguration
public class EnableHazelcastHttpSessionEventsTests {
- private final static String MAX_INACTIVE_INTERVAL_IN_SECONDS_STR = "1";
- private final static int MAX_INACTIVE_INTERVAL_IN_SECONDS = Integer.valueOf(MAX_INACTIVE_INTERVAL_IN_SECONDS_STR);
+ private final static int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
@Autowired
private SessionRepository repository;
@@ -143,9 +142,29 @@ public class EnableHazelcastHttpSessionEventsTests {
assertThat(repository.getSession(sessionToSave.getId())).isNull();
}
+ @Test
+ public void saveUpdatesTimeToLiveTest() throws InterruptedException {
+ S sessionToSave = repository.createSession();
+
+ repository.save(sessionToSave);
+
+ synchronized (lock) {
+ lock.wait((sessionToSave.getMaxInactiveIntervalInSeconds() * 1000) - 500);
+ }
+
+ // Get and save the session like SessionRepositoryFilter would.
+ S sessionToUpdate = repository.getSession(sessionToSave.getId());
+ repository.save(sessionToUpdate);
+
+ synchronized (lock) {
+ lock.wait((sessionToUpdate.getMaxInactiveIntervalInSeconds() * 1000) - 100);
+ }
+
+ assertThat(repository.getSession(sessionToUpdate.getId())).isNotNull();
+ }
@Configuration
- @EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS_STR)
+ @EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
static class HazelcastSessionConfig {
@Bean
diff --git a/spring-session/src/integration-test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationXmlTests.java b/spring-session/src/integration-test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationXmlTests.java
index 6838dbf..a5b2c43 100644
--- a/spring-session/src/integration-test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationXmlTests.java
+++ b/spring-session/src/integration-test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationXmlTests.java
@@ -43,59 +43,6 @@ import com.hazelcast.core.HazelcastInstance;
*/
public class HazelcastHttpSessionConfigurationXmlTests {
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration
- @WebAppConfiguration
- public static class CustomXmlInactiveIntervalTest {
-
- @Autowired
- private SessionRepository repository;
-
- @Autowired
- private HazelcastInstance hazelcast;
-
- @Test
- public void saveSessionTest() throws InterruptedException {
-
- S sessionToSave = repository.createSession();
-
- assertThat(sessionToSave.getMaxInactiveIntervalInSeconds())
- .isEqualTo(150);
-
- repository.save(sessionToSave);
-
- S session = repository.getSession(sessionToSave.getId());
-
- assertThat(session.getId()).isEqualTo(sessionToSave.getId());
- assertThat(session.getMaxInactiveIntervalInSeconds())
- .isEqualTo(150);
- }
-
- @Test
- public void checkUnderlyingMapSettingsTest() {
- assertThat(
- hazelcast.getConfig()
- .getMapConfig("spring:session:sessions")
- .getMaxIdleSeconds())
- .isEqualTo(150);
- }
-
- @Configuration
- @EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = "")
- static class HazelcastSessionXmlConfigCustomIdle {
-
- @Bean
- public HazelcastInstance embeddedHazelcast() {
- Config hazelcastConfig = new ClasspathXmlConfig(
- "org/springframework/session/hazelcast/config/annotation/web/http/hazelcast-custom-idle-time.xml");
- NetworkConfig netConfig = new NetworkConfig();
- netConfig.setPort(SocketUtils.findAvailableTcpPort());
- hazelcastConfig.setNetworkConfig(netConfig);
- return Hazelcast.newHazelcastInstance(hazelcastConfig);
- }
- }
- }
-
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
@@ -104,9 +51,6 @@ public class HazelcastHttpSessionConfigurationXmlTests repository;
- @Autowired
- private HazelcastInstance hazelcast;
-
@Test
public void saveSessionTest() throws InterruptedException {
@@ -120,15 +64,6 @@ public class HazelcastHttpSessionConfigurationXmlTests repository;
- @Autowired
- private HazelcastInstance hazelcast;
-
@Test
public void saveSessionTest() throws InterruptedException {
@@ -169,17 +101,8 @@ public class HazelcastHttpSessionConfigurationXmlTestsIf you wish to use external configuration (outside of this annotation) to set this value, you can
- * set this to "" (an empty String), which will prevent this configuration from overriding
- * the external configuration for this value.
*
* @return the seconds a session can be inactive before expiring
*/
- String maxInactiveIntervalInSeconds() default "1800";
+ int maxInactiveIntervalInSeconds() default 1800;
/**
* This is the name of the Map that will be used in Hazelcast to store the session data.
diff --git a/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java b/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java
index a31a9dc..a09e37c 100644
--- a/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java
+++ b/spring-session/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java
@@ -15,7 +15,10 @@
*/
package org.springframework.session.hazelcast.config.annotation.web.http;
+import java.util.Collection;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
@@ -32,7 +35,6 @@ import org.springframework.session.config.annotation.web.http.SpringHttpSessionC
import org.springframework.session.hazelcast.SessionEntryListener;
import org.springframework.session.web.http.SessionRepositoryFilter;
-import com.hazelcast.config.MapConfig;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
@@ -48,10 +50,6 @@ import com.hazelcast.core.IMap;
@Configuration
public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfiguration implements ImportAware {
- /** This is the magic value to use if you do not want this configuration
- * overriding the maxIdleSeconds value for the Map backing the session data. */
- private static final String DO_NOT_CONFIGURE_INACTIVE_INTERVAL_STRING = "";
-
private Integer maxInactiveIntervalInSeconds = 1800;
private String sessionMapName = "spring:session:sessions";
@@ -62,11 +60,10 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
@Bean
public SessionRepository sessionRepository(HazelcastInstance hazelcastInstance, SessionEntryListener sessionListener) {
- configureSessionMap(hazelcastInstance);
this.sessionsMap = hazelcastInstance.getMap(sessionMapName);
this.sessionListenerUid = this.sessionsMap.addEntryListener(sessionListener, true);
- MapSessionRepository sessionRepository = new MapSessionRepository(this.sessionsMap);
+ MapSessionRepository sessionRepository = new MapSessionRepository(new ExpiringSessionMap(this.sessionsMap));
sessionRepository.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
return sessionRepository;
@@ -82,22 +79,6 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
return new SessionEntryListener(eventPublisher);
}
- /**
- * Make a {@link MapConfig} for the given sessionMapName if one does not exist.
- * Set Hazelcast's maxIdleSeconds to maxInactiveIntervalInSeconds if set (not "").
- * Otherwise get the externally configured maxIdleSeconds for the distributed sessions map.
- *
- * @param hazelcastInstance the {@link HazelcastInstance} to configure
- */
- private void configureSessionMap(HazelcastInstance hazelcastInstance) {
- MapConfig sessionMapConfig = hazelcastInstance.getConfig().getMapConfig(sessionMapName);
- if (this.maxInactiveIntervalInSeconds != null) {
- sessionMapConfig.setMaxIdleSeconds(this.maxInactiveIntervalInSeconds);
- } else {
- this.maxInactiveIntervalInSeconds = sessionMapConfig.getMaxIdleSeconds();
- }
- }
-
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map enableAttrMap = importMetadata.getAnnotationAttributes(EnableHazelcastHttpSession.class.getName());
AnnotationAttributes enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);
@@ -106,19 +87,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
}
private void transferAnnotationAttributes(AnnotationAttributes enableAttrs) {
- String maxInactiveIntervalString = enableAttrs.getString("maxInactiveIntervalInSeconds");
-
- if (DO_NOT_CONFIGURE_INACTIVE_INTERVAL_STRING.equals(maxInactiveIntervalString)) {
- this.maxInactiveIntervalInSeconds = null;
- } else {
- try {
- this.maxInactiveIntervalInSeconds = Integer.parseInt(maxInactiveIntervalString);
- } catch (NumberFormatException nfe) {
- throw new IllegalArgumentException(
- "@EnableHazelcastHttpSession's maxInactiveIntervalInSeconds expects an int format String but was '"
- + maxInactiveIntervalString + "' instead.", nfe);
- }
- }
+ setMaxInactiveIntervalInSeconds((Integer) enableAttrs.getNumber("maxInactiveIntervalInSeconds"));
setSessionMapName(enableAttrs.getString("sessionMapName"));
}
@@ -129,4 +98,70 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
public void setSessionMapName(String sessionMapName) {
this.sessionMapName = sessionMapName;
}
+
+ static class ExpiringSessionMap implements Map {
+ private IMap delegate;
+
+ ExpiringSessionMap(IMap delegate) {
+ this.delegate = delegate;
+ }
+ public ExpiringSession put(String key, ExpiringSession value) {
+ if(value == null) {
+ return delegate.put(key, value);
+ }
+ return delegate.put(key, value, value.getMaxInactiveIntervalInSeconds(), TimeUnit.SECONDS);
+ }
+
+ public int size() {
+ return delegate.size();
+ }
+
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ public boolean containsKey(Object key) {
+ return delegate.containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return delegate.containsValue(value);
+ }
+
+ public ExpiringSession get(Object key) {
+ return delegate.get(key);
+ }
+
+ public ExpiringSession remove(Object key) {
+ return delegate.remove(key);
+ }
+
+ public void putAll(Map extends String, ? extends ExpiringSession> m) {
+ delegate.putAll(m);
+ }
+
+ public void clear() {
+ delegate.clear();
+ }
+
+ public Set keySet() {
+ return delegate.keySet();
+ }
+
+ public Collection values() {
+ return delegate.values();
+ }
+
+ public Set> entrySet() {
+ return delegate.entrySet();
+ }
+
+ public boolean equals(Object o) {
+ return delegate.equals(o);
+ }
+
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+ }
}