Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
245e634bea | ||
|
|
7a2914323f | ||
|
|
6e04d903ae | ||
|
|
d8c3a4dd61 |
@@ -64,6 +64,8 @@ Ensure you have the following in your pom.xml:
|
||||
----
|
||||
endif::[]
|
||||
|
||||
// tag::config[]
|
||||
|
||||
[[security-spring-configuration]]
|
||||
== Spring Configuration
|
||||
|
||||
@@ -79,7 +81,9 @@ include::{docs-test-dir}docs/http/HazelcastHttpSessionConfig.java[tags=config]
|
||||
<1> The `@EnableHazelcastHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
|
||||
In this instance Spring Session is backed by Hazelcast.
|
||||
<2> We create a `HazelcastInstance` that connects Spring Session to Hazelcast.
|
||||
<2> In order to support retrieval of sessions by principal name index, appropriate `ValueExtractor` needs to be registered.
|
||||
Spring Session provides `PrincipalNameExtractor` for this purpose.
|
||||
<3> 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/index.html#hazelcast-configuration[reference documentation].
|
||||
|
||||
@@ -88,8 +92,8 @@ For more information on configuring Hazelcast, refer to the http://docs.hazelcas
|
||||
Our <<security-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
|
||||
|
||||
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
|
||||
Since our application is already loading Spring configuration using our `SecurityInitializer` class, we can simply add our Config class to it.
|
||||
In order for our `Filter` to do its magic, Spring needs to load our `SessionConfig` class.
|
||||
Since our application is already loading Spring configuration using our `SecurityInitializer` class, we can simply add our `SessionConfig` class to it.
|
||||
|
||||
.src/main/java/sample/SecurityInitializer.java
|
||||
[source,java]
|
||||
@@ -113,7 +117,7 @@ NOTE: The name of our class (Initializer) does not matter. What is important is
|
||||
|
||||
By extending `AbstractHttpSessionApplicationInitializer` we ensure that the Spring Bean by the name `springSessionRepositoryFilter` is registered with our Servlet Container for every request before Spring Security's `springSecurityFilterChain`.
|
||||
|
||||
|
||||
// end::config[]
|
||||
|
||||
[[hazelcast-spring-security-sample]]
|
||||
== Hazelcast Spring Security Sample Application
|
||||
@@ -188,4 +192,4 @@ For example, you could delete an individual key as follows (replacing `7e8383a4-
|
||||
|
||||
TIP: The port number of the Hazelcast node will be printed to the console on startup. Replace `xxxxx` above with the port number.
|
||||
|
||||
Now observe that you are no longer authenticated with this session.
|
||||
Now observe that you are no longer authenticated with this session.
|
||||
|
||||
@@ -368,6 +368,18 @@ There is also a constructor taking `Serializer` and `Deserializer` objects, allo
|
||||
You can create your own session converter by extending `AbstractMongoSessionConverter` class.
|
||||
The implementation will be used for serializing, deserializing your objects and for providing queries to access the session.
|
||||
|
||||
[[httpsession-hazelcast]]
|
||||
=== HttpSession with Hazelcast
|
||||
|
||||
Using Spring Session with `HttpSession` is enabled by adding a Servlet Filter before anything that uses the `HttpSession`.
|
||||
|
||||
This section describes how to use Hazelcast to back `HttpSession` using Java based configuration.
|
||||
|
||||
NOTE: The <<samples, Hazelcast Spring Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using Java configuration.
|
||||
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed Hazelcast Spring Guide when integrating with your own application.
|
||||
|
||||
include::guides/hazelcast-spring.adoc[tags=config,leveloffset=+2]
|
||||
|
||||
[[httpsession-how]]
|
||||
=== How HttpSession Integration Works
|
||||
|
||||
@@ -647,44 +659,6 @@ It is important to note that no infrastructure for session expirations is config
|
||||
This is because things like session expiration are highly implementation dependent.
|
||||
This means if you require cleaning up expired sessions, you are responsible for cleaning up the expired sessions.
|
||||
|
||||
[[api-enablehazelcasthttpsession]]
|
||||
=== EnableHazelcastHttpSession
|
||||
|
||||
If you wish to use http://hazelcast.org/[Hazelcast] as your backing source for the `SessionRepository`, then the `@EnableHazelcastHttpSession` annotation
|
||||
can be added to an `@Configuration` class. This extends the functionality provided by the `@EnableSpringHttpSession` annotation but makes the `SessionRepository` for you in Hazelcast.
|
||||
You must provide a single `HazelcastInstance` bean for the configuration to work.
|
||||
For example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
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/index.html#hazelcast-configuration[Hazelcast documentation] for
|
||||
detailed information on configuration options for Hazelcast.
|
||||
|
||||
[[api-enablehazelcasthttpsession-storage]]
|
||||
==== Storage Details
|
||||
|
||||
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 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
|
||||
You can use the following attributes on `@EnableHazelcastHttpSession` to customize the configuration:
|
||||
|
||||
* **maxInactiveIntervalInSeconds** - the amount of time before the session will expire in seconds. Default is 1800 seconds (30 minutes)
|
||||
* **sessionMapName** - the name of the distributed `Map` that will be used in Hazelcast to store the session data.
|
||||
|
||||
[[api-enablehazelcasthttpsession-events]]
|
||||
==== Session Events
|
||||
Using a `MapListener` to respond to entries being added, evicted, and removed from the distributed `Map`, these events will trigger
|
||||
publishing SessionCreatedEvent, SessionExpiredEvent, and SessionDeletedEvent events respectively using the `ApplicationEventPublisher`.
|
||||
|
||||
[[api-redisoperationssessionrepository]]
|
||||
=== RedisOperationsSessionRepository
|
||||
|
||||
@@ -1104,6 +1078,54 @@ include::{session-main-resources-dir}org/springframework/session/jdbc/schema-mys
|
||||
All JDBC operations in `JdbcOperationsSessionRepository` are executed in a transactional manner.
|
||||
Transactions are executed with propagation set to `REQUIRES_NEW` in order to avoid unexpected behavior due to interference with existing transactions (for example, executing `save` operation in a thread that already participates in a read-only transaction).
|
||||
|
||||
[[api-hazelcastsessionrepository]]
|
||||
=== HazelcastSessionRepository
|
||||
|
||||
`HazelcastSessionRepository` is a `SessionRepository` implementation that stores sessions in Hazelcast's distributed `IMap`.
|
||||
In a web environment, this is typically used in combination with `SessionRepositoryFilter`.
|
||||
|
||||
[[api-hazelcastsessionrepository-new]]
|
||||
==== Instantiating a HazelcastSessionRepository
|
||||
|
||||
A typical example of how to create a new instance can be seen below:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{indexdoc-tests}[tags=new-hazelcastsessionrepository]
|
||||
----
|
||||
|
||||
For additional information on how to create and configure Hazelcast instance, refer to the http://docs.hazelcast.org/docs/latest/manual/html-single/index.html#hazelcast-configuration[Hazelcast documentation].
|
||||
|
||||
[[api-enablehazelcasthttpsession]]
|
||||
==== EnableHazelcastHttpSession
|
||||
|
||||
If you wish to use http://hazelcast.org/[Hazelcast] as your backing source for the `SessionRepository`, then the `@EnableHazelcastHttpSession` annotation
|
||||
can be added to an `@Configuration` class. This extends the functionality provided by the `@EnableSpringHttpSession` annotation but makes the `SessionRepository` for you in Hazelcast.
|
||||
You must provide a single `HazelcastInstance` bean for the configuration to work.
|
||||
Complete configuration example can be found in the <<samples>>
|
||||
|
||||
[[api-enablehazelcasthttpsession-storage]]
|
||||
==== Storage Details
|
||||
|
||||
Sessions will be stored in a distributed `IMap` in Hazelcast using a <<api-mapsessionrepository,MapSessionRepository>>.
|
||||
The `IMap` interface methods will be used to `get()` and `put()` Sessions.
|
||||
Additionally, `values()` method is used to support `FindByIndexNameSessionRepository#findByIndexNameAndIndexValue` operation, together with appropriate `ValueExtractor` that needs to be registered with Hazelcast. Refer to <<samples, Hazelcast Spring Sample>> for more details on this configuration.
|
||||
The expiration of a session in the `IMap` is handled by Hazelcast's support for setting the time to live on an entry when it is `put()` into the `IMap`. Entries (sessions) that have been idle longer than the time to live will be automatically removed from the `IMap`.
|
||||
|
||||
You shouldn't need to configure any settings such as `max-idle-seconds` or `time-to-live-seconds` for the `IMap` within the Hazelcast configuration.
|
||||
|
||||
[[api-enablehazelcasthttpsession-customize]]
|
||||
==== Basic Customization
|
||||
You can use the following attributes on `@EnableHazelcastHttpSession` to customize the configuration:
|
||||
|
||||
* **maxInactiveIntervalInSeconds** - the amount of time before the session will expire in seconds. Default is 1800 seconds (30 minutes)
|
||||
* **sessionMapName** - the name of the distributed `Map` that will be used in Hazelcast to store the session data.
|
||||
|
||||
[[api-enablehazelcasthttpsession-events]]
|
||||
==== Session Events
|
||||
Using a `MapListener` to respond to entries being added, evicted, and removed from the distributed `Map`, these events will trigger
|
||||
publishing SessionCreatedEvent, SessionExpiredEvent, and SessionDeletedEvent events respectively using the `ApplicationEventPublisher`.
|
||||
|
||||
[[community]]
|
||||
== Spring Session Community
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package docs;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
@@ -23,10 +27,12 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.MapSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
||||
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
@@ -135,6 +141,25 @@ public class IndexDocTests {
|
||||
// end::new-jdbcoperationssessionrepository[]
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unused")
|
||||
public void newHazelcastSessionRepository() {
|
||||
// tag::new-hazelcastsessionrepository[]
|
||||
|
||||
Config config = new Config();
|
||||
|
||||
// ... configure Hazelcast ...
|
||||
|
||||
HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
|
||||
|
||||
IMap<String, MapSession> sessions = hazelcastInstance
|
||||
.getMap("spring:session:sessions");
|
||||
|
||||
HazelcastSessionRepository repository =
|
||||
new HazelcastSessionRepository(sessions);
|
||||
// end::new-hazelcastsessionrepository[]
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runSpringHttpSessionConfig() {
|
||||
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
|
||||
|
||||
@@ -17,21 +17,37 @@
|
||||
package docs.http;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.MapAttributeConfig;
|
||||
import com.hazelcast.config.MapIndexConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
||||
import org.springframework.session.hazelcast.PrincipalNameExtractor;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
|
||||
//tag::config[]
|
||||
@EnableHazelcastHttpSession // <1>
|
||||
@Configuration
|
||||
public class HazelcastHttpSessionConfig {
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance embeddedHazelcast() {
|
||||
Config hazelcastConfig = new Config();
|
||||
return Hazelcast.newHazelcastInstance(hazelcastConfig); // <2>
|
||||
public HazelcastInstance hazelcastInstance() {
|
||||
MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
.setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractor(PrincipalNameExtractor.class.getName());
|
||||
|
||||
Config config = new Config();
|
||||
|
||||
config.getMapConfig("spring:session:sessions") // <2>
|
||||
.addMapAttributeConfig(attributeConfig)
|
||||
.addMapIndexConfig(new MapIndexConfig(
|
||||
HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
|
||||
return Hazelcast.newHazelcastInstance(config); // <3>
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
@@ -4,14 +4,14 @@ jacksonVersion=2.6.5
|
||||
jspApiVersion=2.0
|
||||
servletApiVersion=3.0.1
|
||||
jstlelVersion=1.2.5
|
||||
version=1.3.0.M1
|
||||
version=1.3.0.M2
|
||||
springDataRedisVersion=1.7.1.RELEASE
|
||||
html5ShivVersion=3.7.3
|
||||
commonsLoggingVersion=1.2
|
||||
junitVersion=4.12
|
||||
gebVersion=0.13.1
|
||||
mockitoVersion=1.10.19
|
||||
hazelcastVersion=3.5.4
|
||||
hazelcastVersion=3.6.5
|
||||
springDataGeodeVersion=1.0.0.APACHE-GEODE-INCUBATING-M2
|
||||
seleniumVersion=2.52.0
|
||||
springSecurityVersion=4.0.3.RELEASE
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.security.web.context.AbstractSecurityWebApplicationIn
|
||||
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
|
||||
|
||||
public SecurityInitializer() {
|
||||
super(SecurityConfig.class, Config.class);
|
||||
super(SecurityConfig.class, SessionConfig.class);
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
|
||||
@@ -16,33 +16,53 @@
|
||||
|
||||
package sample;
|
||||
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.MapAttributeConfig;
|
||||
import com.hazelcast.config.MapIndexConfig;
|
||||
import com.hazelcast.config.SerializerConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
||||
import org.springframework.session.hazelcast.PrincipalNameExtractor;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
// tag::class[]
|
||||
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = 300)
|
||||
@Configuration
|
||||
public class Config {
|
||||
public class SessionConfig {
|
||||
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public HazelcastInstance hazelcastInstance() {
|
||||
com.hazelcast.config.Config cfg = new com.hazelcast.config.Config();
|
||||
NetworkConfig netConfig = new NetworkConfig();
|
||||
netConfig.setPort(SocketUtils.findAvailableTcpPort());
|
||||
System.out.println("Hazelcast port #: " + netConfig.getPort());
|
||||
cfg.setNetworkConfig(netConfig);
|
||||
SerializerConfig serializer = new SerializerConfig().setTypeClass(Object.class)
|
||||
.setImplementation(new ObjectStreamSerializer());
|
||||
cfg.getSerializationConfig().addSerializerConfig(serializer);
|
||||
Config config = new Config();
|
||||
|
||||
return Hazelcast.newHazelcastInstance(cfg);
|
||||
int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
config.getNetworkConfig()
|
||||
.setPort(port);
|
||||
|
||||
System.out.println("Hazelcast port #: " + port);
|
||||
|
||||
SerializerConfig serializer = new SerializerConfig()
|
||||
.setImplementation(new ObjectStreamSerializer())
|
||||
.setTypeClass(Object.class);
|
||||
|
||||
config.getSerializationConfig()
|
||||
.addSerializerConfig(serializer);
|
||||
|
||||
MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
.setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractor(PrincipalNameExtractor.class.getName());
|
||||
|
||||
config.getMapConfig("spring:session:sessions")
|
||||
.addMapAttributeConfig(attributeConfig)
|
||||
.addMapIndexConfig(new MapIndexConfig(
|
||||
HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
|
||||
return Hazelcast.newHazelcastInstance(config);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,7 @@ include 'samples:grails3'
|
||||
include 'spring-session'
|
||||
include 'spring-session-data-gemfire'
|
||||
include 'spring-session-data-geode'
|
||||
include 'spring-session-data-redis'
|
||||
include 'spring-session-jdbc'
|
||||
include 'spring-session-data-mongo'
|
||||
include 'spring-session-data-redis'
|
||||
include 'spring-session-hazelcast'
|
||||
include 'spring-session-jdbc'
|
||||
|
||||
19
spring-session-hazelcast/build.gradle
Normal file
19
spring-session-hazelcast/build.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: MAVEN_GRADLE
|
||||
|
||||
apply plugin: 'spring-io'
|
||||
|
||||
description = "Aggregator for Spring Session and Hazelcast"
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session'),
|
||||
"com.hazelcast:hazelcast:$hazelcastVersion"
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
springIoTestRuntime {
|
||||
imports {
|
||||
mavenBom "io.spring.platform:platform-bom:${springIoVersion}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,7 @@ import com.hazelcast.core.IMap;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -32,20 +31,21 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Tommy Ludwig
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public abstract class AbstractHazelcastRepositoryITests<S extends ExpiringSession> {
|
||||
public abstract class AbstractHazelcastRepositoryITests {
|
||||
|
||||
@Autowired
|
||||
private HazelcastInstance hazelcast;
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
private HazelcastSessionRepository repository;
|
||||
|
||||
@Test
|
||||
public void createAndDestroySession() {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
MapSession sessionToSave = this.repository.createSession();
|
||||
String sessionId = sessionToSave.getId();
|
||||
|
||||
IMap<String, S> hazelcastMap = this.hazelcast.getMap("spring:session:sessions");
|
||||
IMap<String, MapSession> hazelcastMap = this.hazelcast.getMap(
|
||||
"spring:session:sessions");
|
||||
|
||||
assertThat(hazelcastMap.size()).isEqualTo(0);
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -43,8 +42,7 @@ import org.springframework.util.SocketUtils;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class HazelcastClientRepositoryITests<S extends ExpiringSession>
|
||||
extends AbstractHazelcastRepositoryITests<S> {
|
||||
public class HazelcastClientRepositoryITests extends AbstractHazelcastRepositoryITests {
|
||||
|
||||
private static final int PORT = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.session.hazelcast;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.MapAttributeConfig;
|
||||
import com.hazelcast.config.MapIndexConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
@@ -38,8 +40,20 @@ public final class HazelcastITestUtils {
|
||||
* @return the Hazelcast instance
|
||||
*/
|
||||
public static HazelcastInstance embeddedHazelcastServer(int port) {
|
||||
MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
.setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractor(PrincipalNameExtractor.class.getName());
|
||||
|
||||
Config config = new Config();
|
||||
config.getNetworkConfig().setPort(port);
|
||||
|
||||
config.getNetworkConfig()
|
||||
.setPort(port);
|
||||
|
||||
config.getMapConfig("spring:session:sessions")
|
||||
.addMapAttributeConfig(attributeConfig)
|
||||
.addMapIndexConfig(new MapIndexConfig(
|
||||
HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
|
||||
return Hazelcast.newHazelcastInstance(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -37,8 +36,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class HazelcastServerRepositoryITests<S extends ExpiringSession>
|
||||
extends AbstractHazelcastRepositoryITests<S> {
|
||||
public class HazelcastServerRepositoryITests extends AbstractHazelcastRepositoryITests {
|
||||
|
||||
@EnableHazelcastHttpSession
|
||||
@Configuration
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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.hazelcast;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import com.hazelcast.core.EntryEvent;
|
||||
import com.hazelcast.core.IMap;
|
||||
import com.hazelcast.map.listener.EntryAddedListener;
|
||||
import com.hazelcast.map.listener.EntryEvictedListener;
|
||||
import com.hazelcast.map.listener.EntryRemovedListener;
|
||||
import com.hazelcast.query.Predicates;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.session.SessionRepository} implementation that stores
|
||||
* sessions in Hazelcast's distributed {@link IMap}.
|
||||
*
|
||||
* <p>
|
||||
* An example of how to create a new instance can be seen below:
|
||||
*
|
||||
* <pre class="code">
|
||||
* Config config = new Config();
|
||||
*
|
||||
* // ... configure Hazelcast ...
|
||||
*
|
||||
* HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
|
||||
*
|
||||
* IMap{@code <String, MapSession>} sessions = hazelcastInstance
|
||||
* .getMap("spring:session:sessions");
|
||||
*
|
||||
* HazelcastSessionRepository sessionRepository =
|
||||
* new HazelcastSessionRepository(sessions);
|
||||
* </pre>
|
||||
*
|
||||
* In order to support finding sessions by principal name using
|
||||
* {@link #findByIndexNameAndIndexValue(String, String)} method, custom configuration of
|
||||
* {@code IMap} supplied to this implementation is required.
|
||||
*
|
||||
* The following snippet demonstrates how to define required configuration using
|
||||
* programmatic Hazelcast Configuration:
|
||||
*
|
||||
* <pre class="code">
|
||||
* MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
* .setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
* .setExtractor(PrincipalNameExtractor.class.getName());
|
||||
*
|
||||
* Config config = new Config();
|
||||
*
|
||||
* config.getMapConfig("spring:session:sessions")
|
||||
* .addMapAttributeConfig(attributeConfig)
|
||||
* .addMapIndexConfig(new MapIndexConfig(
|
||||
* HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
*
|
||||
* Hazelcast.newHazelcastInstance(config);
|
||||
* </pre>
|
||||
*
|
||||
* This implementation listens for events on the Hazelcast-backed SessionRepository and
|
||||
* translates those events into the corresponding Spring Session events. Publish the
|
||||
* Spring Session events with the given {@link ApplicationEventPublisher}.
|
||||
*
|
||||
* <ul>
|
||||
* <li>entryAdded - {@link SessionCreatedEvent}</li>
|
||||
* <li>entryEvicted - {@link SessionExpiredEvent}</li>
|
||||
* <li>entryRemoved - {@link SessionDeletedEvent}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @author Tommy Ludwig
|
||||
* @author Mark Anderson
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class HazelcastSessionRepository implements
|
||||
FindByIndexNameSessionRepository<MapSession>,
|
||||
EntryAddedListener<String, MapSession>,
|
||||
EntryEvictedListener<String, MapSession>,
|
||||
EntryRemovedListener<String, MapSession> {
|
||||
|
||||
/**
|
||||
* The principal name custom attribute name.
|
||||
*/
|
||||
public static final String PRINCIPAL_NAME_ATTRIBUTE = "principalName";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(HazelcastSessionRepository.class);
|
||||
|
||||
private final IMap<String, MapSession> sessions;
|
||||
|
||||
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
|
||||
|
||||
public void publishEvent(ApplicationEvent event) {
|
||||
}
|
||||
|
||||
public void publishEvent(Object event) {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* If non-null, this value is used to override
|
||||
* {@link MapSession#setMaxInactiveIntervalInSeconds(int)}.
|
||||
*/
|
||||
private Integer defaultMaxInactiveInterval;
|
||||
|
||||
private String sessionListenerId;
|
||||
|
||||
public HazelcastSessionRepository(IMap<String, MapSession> sessions) {
|
||||
Assert.notNull(sessions, "Sessions IMap must not be null");
|
||||
this.sessions = sessions;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
this.sessionListenerId = this.sessions.addEntryListener(this, true);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void close() {
|
||||
this.sessions.removeEntryListener(this.sessionListenerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ApplicationEventPublisher} that is used to publish
|
||||
* {@link AbstractSessionEvent session events}. The default is to not publish session
|
||||
* events.
|
||||
*
|
||||
* @param applicationEventPublisher the {@link ApplicationEventPublisher} that is used
|
||||
* to publish session events. Cannot be null.
|
||||
*/
|
||||
public void setApplicationEventPublisher(
|
||||
ApplicationEventPublisher applicationEventPublisher) {
|
||||
Assert.notNull(applicationEventPublisher,
|
||||
"ApplicationEventPublisher cannot be null");
|
||||
this.eventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum inactive interval in seconds between requests before newly created
|
||||
* sessions will be invalidated. A negative time indicates that the session will never
|
||||
* timeout. The default is 1800 (30 minutes).
|
||||
* @param defaultMaxInactiveInterval the maximum inactive interval in seconds
|
||||
*/
|
||||
public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) {
|
||||
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
|
||||
}
|
||||
|
||||
public MapSession createSession() {
|
||||
MapSession result = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
result.setMaxInactiveIntervalInSeconds(this.defaultMaxInactiveInterval);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void save(MapSession session) {
|
||||
this.sessions.put(session.getId(), session,
|
||||
session.getMaxInactiveIntervalInSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public MapSession getSession(String id) {
|
||||
MapSession saved = this.sessions.get(id);
|
||||
if (saved == null) {
|
||||
return null;
|
||||
}
|
||||
if (saved.isExpired()) {
|
||||
delete(saved.getId());
|
||||
return null;
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
public void delete(String id) {
|
||||
this.sessions.remove(id);
|
||||
}
|
||||
|
||||
public Map<String, MapSession> findByIndexNameAndIndexValue(
|
||||
String indexName, String indexValue) {
|
||||
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Collection<MapSession> sessions = this.sessions.values(
|
||||
Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue));
|
||||
Map<String, MapSession> sessionMap = new HashMap<String, MapSession>(
|
||||
sessions.size());
|
||||
for (MapSession session : sessions) {
|
||||
sessionMap.put(session.getId(), session);
|
||||
}
|
||||
return sessionMap;
|
||||
}
|
||||
|
||||
public void entryAdded(EntryEvent<String, MapSession> event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session created with id: " + event.getValue().getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionCreatedEvent(this, event.getValue()));
|
||||
}
|
||||
|
||||
public void entryEvicted(EntryEvent<String, MapSession> event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session expired with id: " + event.getOldValue().getId());
|
||||
}
|
||||
this.eventPublisher
|
||||
.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
|
||||
}
|
||||
|
||||
public void entryRemoved(EntryEvent<String, MapSession> event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session deleted with id: " + event.getOldValue().getId());
|
||||
}
|
||||
this.eventPublisher
|
||||
.publishEvent(new SessionDeletedEvent(this, event.getOldValue()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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.hazelcast;
|
||||
|
||||
import com.hazelcast.query.extractor.ValueCollector;
|
||||
import com.hazelcast.query.extractor.ValueExtractor;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
|
||||
/**
|
||||
* Hazelcast {@link ValueExtractor} responsible for extracting principal name from the
|
||||
* {@link MapSession}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class PrincipalNameExtractor extends ValueExtractor<MapSession, String> {
|
||||
|
||||
private static final PrincipalNameResolver PRINCIPAL_NAME_RESOLVER =
|
||||
new PrincipalNameResolver();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void extract(MapSession target, String argument,
|
||||
ValueCollector collector) {
|
||||
String principalName = PRINCIPAL_NAME_RESOLVER.resolvePrincipal(target);
|
||||
if (principalName != null) {
|
||||
collector.addObject(principalName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the Spring Security principal name.
|
||||
*/
|
||||
static class PrincipalNameResolver {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
public String resolvePrincipal(Session session) {
|
||||
String principalName = session.getAttribute(
|
||||
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
||||
if (principalName != null) {
|
||||
return principalName;
|
||||
}
|
||||
Object authentication = session.getAttribute(SPRING_SECURITY_CONTEXT);
|
||||
if (authentication != null) {
|
||||
Expression expression = this.parser
|
||||
.parseExpression("authentication?.name");
|
||||
return expression.getValue(authentication, String.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,7 +43,9 @@ import org.springframework.util.Assert;
|
||||
* @author Tommy Ludwig
|
||||
* @author Mark Anderson
|
||||
* @since 1.1
|
||||
* @deprecated Use {@link HazelcastSessionRepository} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class SessionEntryListener implements EntryAddedListener<String, ExpiringSession>,
|
||||
EntryEvictedListener<String, ExpiringSession>,
|
||||
EntryRemovedListener<String, ExpiringSession> {
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
|
||||
/**
|
||||
@@ -56,19 +57,20 @@ import org.springframework.session.config.annotation.web.http.EnableSpringHttpSe
|
||||
@Import(HazelcastHttpSessionConfiguration.class)
|
||||
@Configuration
|
||||
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.
|
||||
*
|
||||
* @return the seconds a session can be inactive before expiring
|
||||
*/
|
||||
int maxInactiveIntervalInSeconds() default 1800;
|
||||
int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||
|
||||
/**
|
||||
* This is the name of the Map that will be used in Hazelcast to store the session
|
||||
* data. Default is "spring:session:sessions".
|
||||
* data. Default is {@link HazelcastHttpSessionConfiguration#DEFAULT_SESSION_MAP_NAME}.
|
||||
* @return the name of the Map to store the sessions in Hazelcast
|
||||
*/
|
||||
String sessionMapName() default "spring:session:sessions";
|
||||
String sessionMapName() default HazelcastHttpSessionConfiguration.DEFAULT_SESSION_MAP_NAME;
|
||||
|
||||
}
|
||||
|
||||
@@ -16,12 +16,7 @@
|
||||
|
||||
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;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
@@ -32,11 +27,9 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.MapSessionRepository;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.hazelcast.SessionEntryListener;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
|
||||
/**
|
||||
@@ -45,6 +38,7 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
* {@link HazelcastInstance} must be exposed as a Bean.
|
||||
*
|
||||
* @author Tommy Ludwig
|
||||
* @author Vedran Pavic
|
||||
* @since 1.1
|
||||
* @see EnableHazelcastHttpSession
|
||||
*/
|
||||
@@ -52,49 +46,30 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
implements ImportAware {
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = 1800;
|
||||
static final String DEFAULT_SESSION_MAP_NAME = "spring:session:sessions";
|
||||
|
||||
private String sessionMapName = "spring:session:sessions";
|
||||
private Integer maxInactiveIntervalInSeconds;
|
||||
|
||||
private String sessionListenerUid;
|
||||
|
||||
private IMap<String, ExpiringSession> sessionsMap;
|
||||
private String sessionMapName = DEFAULT_SESSION_MAP_NAME;
|
||||
|
||||
@Bean
|
||||
public SessionRepository<ExpiringSession> sessionRepository(
|
||||
HazelcastInstance hazelcastInstance, SessionEntryListener sessionListener) {
|
||||
this.sessionsMap = hazelcastInstance.getMap(this.sessionMapName);
|
||||
this.sessionListenerUid = this.sessionsMap.addEntryListener(sessionListener,
|
||||
true);
|
||||
|
||||
MapSessionRepository sessionRepository = new MapSessionRepository(
|
||||
new ExpiringSessionMap(this.sessionsMap));
|
||||
sessionRepository
|
||||
.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void removeSessionListener() {
|
||||
this.sessionsMap.removeEntryListener(this.sessionListenerUid);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionEntryListener sessionListener(
|
||||
public HazelcastSessionRepository sessionRepository(
|
||||
HazelcastInstance hazelcastInstance,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
return new SessionEntryListener(eventPublisher);
|
||||
IMap<String, MapSession> sessions = hazelcastInstance.getMap(
|
||||
this.sessionMapName);
|
||||
HazelcastSessionRepository sessionRepository = new HazelcastSessionRepository(
|
||||
sessions);
|
||||
sessionRepository.setApplicationEventPublisher(eventPublisher);
|
||||
sessionRepository.setDefaultMaxInactiveInterval(
|
||||
this.maxInactiveIntervalInSeconds);
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> enableAttrMap = importMetadata
|
||||
.getAnnotationAttributes(EnableHazelcastHttpSession.class.getName());
|
||||
AnnotationAttributes enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);
|
||||
|
||||
transferAnnotationAttributes(enableAttrs);
|
||||
}
|
||||
|
||||
private void transferAnnotationAttributes(AnnotationAttributes enableAttrs) {
|
||||
setMaxInactiveIntervalInSeconds(
|
||||
(Integer) enableAttrs.getNumber("maxInactiveIntervalInSeconds"));
|
||||
setSessionMapName(enableAttrs.getString("sessionMapName"));
|
||||
@@ -108,74 +83,4 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
|
||||
this.sessionMapName = sessionMapName;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for Hazelcast's {@link IMap} which is used to store the sessions.
|
||||
*/
|
||||
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 this.delegate.put(key, value);
|
||||
}
|
||||
return this.delegate.put(key, value, value.getMaxInactiveIntervalInSeconds(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.delegate.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.delegate.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
return this.delegate.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean containsValue(Object value) {
|
||||
return this.delegate.containsValue(value);
|
||||
}
|
||||
|
||||
public ExpiringSession get(Object key) {
|
||||
return this.delegate.get(key);
|
||||
}
|
||||
|
||||
public ExpiringSession remove(Object key) {
|
||||
return this.delegate.remove(key);
|
||||
}
|
||||
|
||||
public void putAll(Map<? extends String, ? extends ExpiringSession> m) {
|
||||
this.delegate.putAll(m);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.delegate.clear();
|
||||
}
|
||||
|
||||
public Set<String> keySet() {
|
||||
return this.delegate.keySet();
|
||||
}
|
||||
|
||||
public Collection<ExpiringSession> values() {
|
||||
return this.delegate.values();
|
||||
}
|
||||
|
||||
public Set<java.util.Map.Entry<String, ExpiringSession>> entrySet() {
|
||||
return this.delegate.entrySet();
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return this.delegate.equals(o);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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.hazelcast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.hazelcast.core.IMap;
|
||||
import com.hazelcast.query.impl.predicates.EqualPredicate;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link HazelcastSessionRepository}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HazelcastSessionRepositoryTests {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private IMap<String, MapSession> sessions;
|
||||
|
||||
private HazelcastSessionRepository repository;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.repository = new HazelcastSessionRepository(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullHazelcastInstance() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Sessions IMap must not be null");
|
||||
|
||||
new HazelcastSessionRepository(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionDefaultMaxInactiveInterval() throws Exception {
|
||||
MapSession session = this.repository.createSession();
|
||||
|
||||
assertThat(session.getMaxInactiveIntervalInSeconds())
|
||||
.isEqualTo(new MapSession().getMaxInactiveIntervalInSeconds());
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionCustomMaxInactiveInterval() throws Exception {
|
||||
int interval = 1;
|
||||
this.repository.setDefaultMaxInactiveInterval(interval);
|
||||
|
||||
MapSession session = this.repository.createSession();
|
||||
|
||||
assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(interval);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveNew() {
|
||||
MapSession session = this.repository.createSession();
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session),
|
||||
isA(Long.class), eq(TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveUpdatedAttributes() {
|
||||
MapSession session = new MapSession();
|
||||
session.setAttribute("testName", "testValue");
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session),
|
||||
isA(Long.class), eq(TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveUpdatedLastAccessedTime() {
|
||||
MapSession session = new MapSession();
|
||||
session.setLastAccessedTime(System.currentTimeMillis());
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session),
|
||||
isA(Long.class), eq(TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveUnchanged() {
|
||||
MapSession session = new MapSession();
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
verify(this.sessions, times(1)).put(eq(session.getId()), eq(session),
|
||||
isA(Long.class), eq(TimeUnit.SECONDS));
|
||||
// TODO - once save optimization is implemented, should be replaced with:
|
||||
//verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionNotFound() {
|
||||
String sessionId = "testSessionId";
|
||||
|
||||
MapSession session = this.repository.getSession(sessionId);
|
||||
|
||||
assertThat(session).isNull();
|
||||
verify(this.sessions, times(1)).get(eq(sessionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionExpired() {
|
||||
MapSession expired = new MapSession();
|
||||
expired.setLastAccessedTime(System.currentTimeMillis() -
|
||||
(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS * 1000 + 1000));
|
||||
given(this.sessions.get(eq(expired.getId()))).willReturn(expired);
|
||||
|
||||
MapSession session = this.repository.getSession(expired.getId());
|
||||
|
||||
assertThat(session).isNull();
|
||||
verify(this.sessions, times(1)).get(eq(expired.getId()));
|
||||
verify(this.sessions, times(1)).remove(eq(expired.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionFound() {
|
||||
MapSession saved = new MapSession();
|
||||
saved.setAttribute("savedName", "savedValue");
|
||||
given(this.sessions.get(eq(saved.getId()))).willReturn(saved);
|
||||
|
||||
MapSession session = this.repository.getSession(saved.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(saved.getId());
|
||||
assertThat(session.getAttribute("savedName")).isEqualTo("savedValue");
|
||||
verify(this.sessions, times(1)).get(eq(saved.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() {
|
||||
String sessionId = "testSessionId";
|
||||
|
||||
this.repository.delete(sessionId);
|
||||
|
||||
verify(this.sessions, times(1)).remove(eq(sessionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIndexNameAndIndexValueUnknownIndexName() {
|
||||
String indexValue = "testIndexValue";
|
||||
|
||||
Map<String, MapSession> sessions = this.repository.findByIndexNameAndIndexValue(
|
||||
"testIndexName", indexValue);
|
||||
|
||||
assertThat(sessions).isEmpty();
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
|
||||
String principal = "username";
|
||||
|
||||
Map<String, MapSession> sessions = this.repository.findByIndexNameAndIndexValue(
|
||||
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
|
||||
assertThat(sessions).isEmpty();
|
||||
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIndexNameAndIndexValuePrincipalIndexNameFound() {
|
||||
String principal = "username";
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(principal,
|
||||
"notused", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
List<MapSession> saved = new ArrayList<MapSession>(2);
|
||||
MapSession saved1 = new MapSession();
|
||||
saved1.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||
saved.add(saved1);
|
||||
MapSession saved2 = new MapSession();
|
||||
saved2.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||
saved.add(saved2);
|
||||
given(this.sessions.values(isA(EqualPredicate.class))).willReturn(saved);
|
||||
|
||||
Map<String, MapSession> sessions = this.repository.findByIndexNameAndIndexValue(
|
||||
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
|
||||
assertThat(sessions).hasSize(2);
|
||||
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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.hazelcast.config.annotation.web.http;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.isA;
|
||||
|
||||
/**
|
||||
* Tests for {@link HazelcastHttpSessionConfiguration}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HazelcastHttpSessionConfigurationTests {
|
||||
|
||||
private static final String MAP_NAME = "spring:test:sessions";
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private static HazelcastInstance hazelcastInstance;
|
||||
|
||||
@Mock
|
||||
private IMap<Object, Object> sessions;
|
||||
|
||||
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
given(hazelcastInstance.getMap(isA(String.class))).willReturn(this.sessions);
|
||||
}
|
||||
|
||||
@After
|
||||
public void closeContext() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noHazelcastInstanceConfiguration() {
|
||||
this.thrown.expect(UnsatisfiedDependencyException.class);
|
||||
this.thrown.expectMessage("HazelcastInstance");
|
||||
|
||||
registerAndRefresh(EmptyConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultConfiguration() {
|
||||
registerAndRefresh(DefaultConfiguration.class);
|
||||
|
||||
assertThat(this.context.getBean(HazelcastSessionRepository.class))
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customTableName() {
|
||||
registerAndRefresh(CustomSessionMapNameConfiguration.class);
|
||||
|
||||
HazelcastSessionRepository repository = this.context
|
||||
.getBean(HazelcastSessionRepository.class);
|
||||
HazelcastHttpSessionConfiguration configuration = this.context
|
||||
.getBean(HazelcastHttpSessionConfiguration.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "sessionMapName"))
|
||||
.isEqualTo(MAP_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCustomSessionMapName() {
|
||||
registerAndRefresh(BaseConfiguration.class,
|
||||
CustomSessionMapNameSetConfiguration.class);
|
||||
|
||||
HazelcastSessionRepository repository = this.context
|
||||
.getBean(HazelcastSessionRepository.class);
|
||||
HazelcastHttpSessionConfiguration configuration = this.context
|
||||
.getBean(HazelcastHttpSessionConfiguration.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "sessionMapName"))
|
||||
.isEqualTo(MAP_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCustomMaxInactiveIntervalInSeconds() {
|
||||
registerAndRefresh(BaseConfiguration.class,
|
||||
CustomMaxInactiveIntervalInSecondsSetConfiguration.class);
|
||||
|
||||
HazelcastSessionRepository repository = this.context
|
||||
.getBean(HazelcastSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval")).isEqualTo(
|
||||
MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customMaxInactiveIntervalInSeconds() {
|
||||
registerAndRefresh(CustomMaxInactiveIntervalInSecondsConfiguration.class);
|
||||
|
||||
HazelcastSessionRepository repository = this.context
|
||||
.getBean(HazelcastSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval"))
|
||||
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
this.context.register(annotatedClasses);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession
|
||||
static class EmptyConfiguration {
|
||||
}
|
||||
|
||||
static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance hazelcastInstance() {
|
||||
return hazelcastInstance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession
|
||||
static class DefaultConfiguration extends BaseConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession(sessionMapName = MAP_NAME)
|
||||
static class CustomSessionMapNameConfiguration extends BaseConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomSessionMapNameSetConfiguration
|
||||
extends HazelcastHttpSessionConfiguration {
|
||||
|
||||
CustomSessionMapNameSetConfiguration() {
|
||||
setSessionMapName(MAP_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomMaxInactiveIntervalInSecondsSetConfiguration
|
||||
extends HazelcastHttpSessionConfiguration {
|
||||
|
||||
CustomMaxInactiveIntervalInSecondsSetConfiguration() {
|
||||
setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||
static class CustomMaxInactiveIntervalInSecondsConfiguration
|
||||
extends BaseConfiguration {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.5.xsd"
|
||||
xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.6.xsd">
|
||||
|
||||
<group>
|
||||
<name>spring-session-it-test-idle-time-map-name</name>
|
||||
<password>test-pass</password>
|
||||
</group>
|
||||
|
||||
<network>
|
||||
<port auto-increment="true" port-count="100">5701</port>
|
||||
<outbound-ports>
|
||||
@@ -24,14 +26,20 @@
|
||||
</aws>
|
||||
</join>
|
||||
</network>
|
||||
|
||||
<map name="test-sessions">
|
||||
<in-memory-format>BINARY</in-memory-format>
|
||||
<backup-count>1</backup-count>
|
||||
<async-backup-count>0</async-backup-count>
|
||||
<time-to-live-seconds>0</time-to-live-seconds>
|
||||
<max-idle-seconds>300</max-idle-seconds>
|
||||
<merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
|
||||
</merge-policy>
|
||||
<merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
|
||||
<attributes>
|
||||
<attribute extractor="org.springframework.session.hazelcast.PrincipalNameExtractor">principalName</attribute>
|
||||
</attributes>
|
||||
<indexes>
|
||||
<index ordered="false">principalName</index>
|
||||
</indexes>
|
||||
</map>
|
||||
|
||||
</hazelcast>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.5.xsd"
|
||||
xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<group>
|
||||
<name>spring-session-it-test-idle-time</name>
|
||||
<password>test-pass</password>
|
||||
</group>
|
||||
<network>
|
||||
<port auto-increment="true" port-count="100">5701</port>
|
||||
<outbound-ports>
|
||||
<ports>0</ports>
|
||||
</outbound-ports>
|
||||
<join>
|
||||
<multicast enabled="false">
|
||||
</multicast>
|
||||
<tcp-ip enabled="true">
|
||||
<interface>127.0.0.1</interface>
|
||||
<member-list>
|
||||
<member>127.0.0.1</member>
|
||||
</member-list>
|
||||
</tcp-ip>
|
||||
<aws enabled="false">
|
||||
</aws>
|
||||
</join>
|
||||
</network>
|
||||
<map name="spring:session:sessions">
|
||||
<in-memory-format>BINARY</in-memory-format>
|
||||
<backup-count>1</backup-count>
|
||||
<async-backup-count>0</async-backup-count>
|
||||
<time-to-live-seconds>0</time-to-live-seconds>
|
||||
<max-idle-seconds>150</max-idle-seconds>
|
||||
<merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
|
||||
</merge-policy>
|
||||
</map>
|
||||
|
||||
</hazelcast>
|
||||
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.5.xsd"
|
||||
xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.6.xsd">
|
||||
|
||||
<group>
|
||||
<name>spring-session-it-test-map-name</name>
|
||||
<password>test-pass</password>
|
||||
</group>
|
||||
|
||||
<network>
|
||||
<port auto-increment="true" port-count="100">5701</port>
|
||||
<outbound-ports>
|
||||
@@ -24,14 +26,20 @@
|
||||
</aws>
|
||||
</join>
|
||||
</network>
|
||||
|
||||
<map name="my-sessions">
|
||||
<in-memory-format>BINARY</in-memory-format>
|
||||
<backup-count>1</backup-count>
|
||||
<async-backup-count>0</async-backup-count>
|
||||
<time-to-live-seconds>0</time-to-live-seconds>
|
||||
<max-idle-seconds>0</max-idle-seconds>
|
||||
<merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
|
||||
</merge-policy>
|
||||
<merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
|
||||
<attributes>
|
||||
<attribute extractor="org.springframework.session.hazelcast.PrincipalNameExtractor">principalName</attribute>
|
||||
</attributes>
|
||||
<indexes>
|
||||
<index ordered="false">principalName</index>
|
||||
</indexes>
|
||||
</map>
|
||||
|
||||
</hazelcast>
|
||||
|
||||
Reference in New Issue
Block a user