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
This commit is contained in:
Mark Anderson
2016-02-01 19:10:09 +00:00
committed by Rob Winch
parent 2b5386ad98
commit f2443f5e21
7 changed files with 107 additions and 132 deletions

View File

@@ -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):

View File

@@ -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 <<api-mapsessionrepository,MapSessionRepository>>.
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

View File

@@ -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 {

View File

@@ -59,8 +59,7 @@ import com.hazelcast.core.HazelcastInstance;
@WebAppConfiguration
public class EnableHazelcastHttpSessionEventsTests<S extends ExpiringSession> {
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<S> repository;
@@ -143,9 +142,29 @@ public class EnableHazelcastHttpSessionEventsTests<S extends ExpiringSession> {
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

View File

@@ -43,59 +43,6 @@ import com.hazelcast.core.HazelcastInstance;
*/
public class HazelcastHttpSessionConfigurationXmlTests<S extends ExpiringSession> {
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public static class CustomXmlInactiveIntervalTest<S extends ExpiringSession> {
@Autowired
private SessionRepository<S> 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<S extends ExpiringSession
@Autowired
private SessionRepository<S> repository;
@Autowired
private HazelcastInstance hazelcast;
@Test
public void saveSessionTest() throws InterruptedException {
@@ -120,15 +64,6 @@ public class HazelcastHttpSessionConfigurationXmlTests<S extends ExpiringSession
assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(1800);
}
@Test
public void checkUnderlyingMapSettingsTest() {
assertThat(
hazelcast.getConfig()
.getMapConfig("my-sessions")
.getMaxIdleSeconds())
.isEqualTo(1800);
}
@Configuration
@EnableHazelcastHttpSession(sessionMapName = "my-sessions")
static class HazelcastSessionXmlConfigCustomMapName {
@@ -153,9 +88,6 @@ public class HazelcastHttpSessionConfigurationXmlTests<S extends ExpiringSession
@Autowired
private SessionRepository<S> repository;
@Autowired
private HazelcastInstance hazelcast;
@Test
public void saveSessionTest() throws InterruptedException {
@@ -169,17 +101,8 @@ public class HazelcastHttpSessionConfigurationXmlTests<S extends ExpiringSession
assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(1200);
}
@Test
public void checkUnderlyingMapSettingsTest() {
assertThat(
hazelcast.getConfig()
.getMapConfig("test-sessions")
.getMaxIdleSeconds())
.isEqualTo(1200);
}
@Configuration
@EnableHazelcastHttpSession(sessionMapName = "test-sessions", maxInactiveIntervalInSeconds = "1200")
@EnableHazelcastHttpSession(sessionMapName = "test-sessions", maxInactiveIntervalInSeconds = 1200)
static class HazelcastSessionXmlConfigCustomMapNameAndIdle {
@Bean

View File

@@ -59,13 +59,10 @@ public @interface EnableHazelcastHttpSession {
/**
* This is the session timeout in seconds. By default, it is set to 1800 seconds (30 minutes).
* This should be a non-negative integer.
* <p>If 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.</p>
*
* @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.

View File

@@ -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<ExpiringSession> 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<String, Object> 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<String, ExpiringSession> {
private IMap<String,ExpiringSession> delegate;
ExpiringSessionMap(IMap<String,ExpiringSession> 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<String> keySet() {
return delegate.keySet();
}
public Collection<ExpiringSession> values() {
return delegate.values();
}
public Set<java.util.Map.Entry<String, ExpiringSession>> entrySet() {
return delegate.entrySet();
}
public boolean equals(Object o) {
return delegate.equals(o);
}
public int hashCode() {
return delegate.hashCode();
}
}
}