Compare commits

..

4 Commits

Author SHA1 Message Date
Spring Buildmaster
245e634bea Release version 1.3.0.M2 2016-09-14 19:08:14 +00:00
Rob Winch
7a2914323f Update to Hazelcast 3.6.5
Issue gh-544
2016-09-14 11:32:03 -05:00
Vedran Pavic
6e04d903ae Add HazelcastSessionRepository
This commit improves existing Hazelcast support, which is based on
MapSessionRepository, with dedicated HazelcastSessionRepository
that implements the FindByIndexNameSessionRepository contract.

Also a new hazelcast-spring-session module was added to provide
dependency management for Hazelcast support.

Fixes gh-544
2016-09-14 11:22:56 -05:00
Spring Buildmaster
d8c3a4dd61 Next development version 2016-09-13 21:21:21 +00:00
23 changed files with 990 additions and 236 deletions

View File

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

View File

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

View File

@@ -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();

View File

@@ -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[]

View File

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

View File

@@ -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[]

View File

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

View File

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

View 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}"
}
}
}

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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