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:
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user