Add support for Hazelcast 4
Closes gh-1584
This commit is contained in:
@@ -5,6 +5,8 @@ include 'spring-session-data-redis'
|
||||
include 'spring-session-docs'
|
||||
include 'spring-session-hazelcast'
|
||||
include 'spring-session-jdbc'
|
||||
include 'hazelcast4'
|
||||
project(':hazelcast4').projectDir = file('spring-session-hazelcast/hazelcast4')
|
||||
|
||||
file('spring-session-samples').eachDirMatch(~/spring-session-sample-.*/) { dir ->
|
||||
include dir.name
|
||||
|
||||
37
spring-session-hazelcast/hazelcast4/hazelcast4.gradle
Normal file
37
spring-session-hazelcast/hazelcast4/hazelcast4.gradle
Normal file
@@ -0,0 +1,37 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'io.spring.convention.repository'
|
||||
id 'io.spring.convention.springdependencymangement'
|
||||
id 'io.spring.convention.dependency-set'
|
||||
id 'io.spring.convention.checkstyle'
|
||||
id 'io.spring.convention.tests-configuration'
|
||||
id 'io.spring.convention.integration-test'
|
||||
}
|
||||
|
||||
configurations {
|
||||
classesOnlyElements {
|
||||
canBeConsumed = true
|
||||
canBeResolved = false
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
classesOnlyElements(compileJava.destinationDir)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-core')
|
||||
compile "com.hazelcast:hazelcast:4.0.2"
|
||||
compile "org.springframework:spring-context"
|
||||
|
||||
testCompile "javax.servlet:javax.servlet-api"
|
||||
testCompile "org.springframework:spring-web"
|
||||
testCompile "org.junit.jupiter:junit-jupiter-api"
|
||||
testCompile "org.springframework.security:spring-security-core"
|
||||
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
||||
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
integrationTestCompile "com.hazelcast:hazelcast:4.0.2"
|
||||
integrationTestCompile project(":spring-session-hazelcast")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.core.HazelcastInstance;
|
||||
import com.hazelcast.map.IMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.hazelcast.Hazelcast4IndexedSessionRepository.HazelcastSession;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Base class for {@link Hazelcast4IndexedSessionRepository} integration tests.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
abstract class AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
@Autowired
|
||||
private HazelcastInstance hazelcastInstance;
|
||||
|
||||
@Autowired
|
||||
private Hazelcast4IndexedSessionRepository repository;
|
||||
|
||||
@Test
|
||||
void createAndDestroySession() {
|
||||
HazelcastSession sessionToSave = this.repository.createSession();
|
||||
String sessionId = sessionToSave.getId();
|
||||
|
||||
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||
.getMap(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||
|
||||
assertThat(hazelcastMap.size()).isEqualTo(0);
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(hazelcastMap.size()).isEqualTo(1);
|
||||
assertThat(hazelcastMap.get(sessionId)).isEqualTo(sessionToSave);
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
|
||||
assertThat(hazelcastMap.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeSessionIdWhenOnlyChangeId() {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(attrName, attrValue);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
HazelcastSession findById = this.repository.findById(toSave.getId());
|
||||
|
||||
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById);
|
||||
|
||||
assertThat(this.repository.findById(originalFindById)).isNull();
|
||||
|
||||
HazelcastSession findByChangeSessionId = this.repository.findById(changeSessionId);
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
this.repository.deleteById(changeSessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeSessionIdWhenChangeTwice() {
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
String originalId = toSave.getId();
|
||||
String changeId1 = toSave.changeSessionId();
|
||||
String changeId2 = toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
assertThat(this.repository.findById(changeId1)).isNull();
|
||||
assertThat(this.repository.findById(changeId2)).isNotNull();
|
||||
|
||||
this.repository.deleteById(changeId2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeSessionIdWhenSetAttributeOnChangedSession() {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
HazelcastSession findById = this.repository.findById(toSave.getId());
|
||||
|
||||
findById.setAttribute(attrName, attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById);
|
||||
|
||||
assertThat(this.repository.findById(originalFindById)).isNull();
|
||||
|
||||
HazelcastSession findByChangeSessionId = this.repository.findById(changeSessionId);
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
this.repository.deleteById(changeSessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeSessionIdWhenHasNotSaved() {
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
String originalId = toSave.getId();
|
||||
toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId())).isNotNull();
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
|
||||
this.repository.deleteById(toSave.getId());
|
||||
}
|
||||
|
||||
@Test // gh-1076
|
||||
void attemptToUpdateSessionAfterDelete() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
this.repository.save(session);
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setAttribute("attributeName", "attributeValue");
|
||||
this.repository.deleteById(sessionId);
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(sessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAndUpdateSession() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setAttribute("attributeName", "attributeValue");
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionWithSecurityContextAndFindById() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken("saves-" + System.currentTimeMillis(),
|
||||
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(authentication);
|
||||
session.setAttribute(SPRING_SECURITY_CONTEXT, securityContext);
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.client.HazelcastClient;
|
||||
import com.hazelcast.client.config.ClientConfig;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link Hazelcast4IndexedSessionRepository} using client-server
|
||||
* topology.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class ClientServerHazelcast4IndexedSessionRepositoryITests extends AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||
|
||||
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:4.0.2")
|
||||
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
|
||||
"/opt/hazelcast/hazelcast.xml");
|
||||
|
||||
@BeforeAll
|
||||
static void setUpClass() {
|
||||
container.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownClass() {
|
||||
container.stop();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance hazelcastInstance() {
|
||||
ClientConfig clientConfig = new ClientConfig();
|
||||
clientConfig.getNetworkConfig()
|
||||
.addAddress(container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
|
||||
clientConfig.getUserCodeDeploymentConfig().setEnabled(true).addClass(Session.class)
|
||||
.addClass(MapSession.class).addClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||
return HazelcastClient.newHazelcastClient(clientConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link Hazelcast4IndexedSessionRepository} using embedded
|
||||
* topology.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class EmbeddedHazelcast4IndexedSessionRepositoryITests extends AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||
|
||||
@EnableHazelcastHttpSession
|
||||
@Configuration
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance hazelcastInstance() {
|
||||
return Hazelcast4ITestUtils.embeddedHazelcastServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.config.AttributeConfig;
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.IndexConfig;
|
||||
import com.hazelcast.config.IndexType;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.config.SerializerConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
/**
|
||||
* Utility class for Hazelcast integration tests.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
final class Hazelcast4ITestUtils {
|
||||
|
||||
private Hazelcast4ITestUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link HazelcastInstance} for use in integration tests.
|
||||
* @return the Hazelcast instance
|
||||
*/
|
||||
static HazelcastInstance embeddedHazelcastServer() {
|
||||
Config config = new Config();
|
||||
NetworkConfig networkConfig = config.getNetworkConfig();
|
||||
networkConfig.setPort(0);
|
||||
networkConfig.getJoin().getMulticastConfig().setEnabled(false);
|
||||
AttributeConfig attributeConfig = new AttributeConfig()
|
||||
.setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
|
||||
config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||
.addAttributeConfig(attributeConfig).addIndexConfig(
|
||||
new IndexConfig(IndexType.HASH, Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||
SerializerConfig serializerConfig = new SerializerConfig();
|
||||
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||
config.getSerializationConfig().addSerializerConfig(serializerConfig);
|
||||
return Hazelcast.newHazelcastInstance(config);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Ensure that the appropriate SessionEvents are fired at the expected times. Additionally
|
||||
* ensure that the interactions with the {@link SessionRepository} abstraction behave as
|
||||
* expected after each SessionEvent.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class SessionEventHazelcast4IndexedSessionRepositoryTests<S extends Session> {
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@Autowired
|
||||
private SessionEventRegistry registry;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.registry.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveSessionTest() throws InterruptedException {
|
||||
String username = "saves-" + System.currentTimeMillis();
|
||||
|
||||
S sessionToSave = this.repository.createSession();
|
||||
|
||||
String expectedAttributeName = "a";
|
||||
String expectedAttributeValue = "b";
|
||||
sessionToSave.setAttribute(expectedAttributeName, expectedAttributeValue);
|
||||
Authentication toSaveToken = new UsernamePasswordAuthenticationToken(username, "password",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
|
||||
toSaveContext.setAuthentication(toSaveToken);
|
||||
sessionToSave.setAttribute("SPRING_SECURITY_CONTEXT", toSaveContext);
|
||||
sessionToSave.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
|
||||
Session session = this.repository.findById(sessionToSave.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(sessionToSave.getId());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(sessionToSave.getAttributeNames());
|
||||
assertThat(session.<String>getAttribute(expectedAttributeName))
|
||||
.isEqualTo(sessionToSave.getAttribute(expectedAttributeName));
|
||||
}
|
||||
|
||||
@Test
|
||||
void expiredSessionTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
assertThat(sessionToSave.getMaxInactiveInterval())
|
||||
.isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionExpiredEvent.class);
|
||||
|
||||
assertThat(this.repository.findById(sessionToSave.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletedSessionTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
this.repository.deleteById(sessionToSave.getId());
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionDeletedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionDeletedEvent.class);
|
||||
|
||||
assertThat(this.repository.findById(sessionToSave.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatesTimeToLiveTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
sessionToSave.setMaxInactiveInterval(Duration.ofSeconds(3));
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
// Get and save the session like SessionRepositoryFilter would.
|
||||
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
|
||||
sessionToUpdate.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(sessionToUpdate);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
assertThat(this.repository.findById(sessionToUpdate.getId())).isNotNull();
|
||||
}
|
||||
|
||||
@Test // gh-1077
|
||||
void changeSessionIdNoEventTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
sessionToSave.setMaxInactiveInterval(Duration.ofMinutes(30));
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
sessionToSave.changeSessionId();
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-1300
|
||||
@Disabled("See https://github.com/hazelcast/hazelcast/issues/16987")
|
||||
void updateMaxInactiveIntervalTest() throws InterruptedException {
|
||||
S sessionToSave = this.repository.createSession();
|
||||
sessionToSave.setMaxInactiveInterval(Duration.ofMinutes(30));
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
this.registry.clear();
|
||||
|
||||
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
|
||||
sessionToUpdate.setLastAccessedTime(Instant.now());
|
||||
sessionToUpdate.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||
this.repository.save(sessionToUpdate);
|
||||
|
||||
assertThat(this.registry.receivedEvent(sessionToUpdate.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToUpdate.getId()))
|
||||
.isInstanceOf(SessionExpiredEvent.class);
|
||||
assertThat(this.repository.findById(sessionToUpdate.getId())).isNull();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance embeddedHazelcast() {
|
||||
return Hazelcast4ITestUtils.embeddedHazelcastServer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SessionEventRegistry sessionEventRegistry() {
|
||||
return new SessionEventRegistry();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
|
||||
class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||
|
||||
private Map<String, AbstractSessionEvent> events = new HashMap<>();
|
||||
|
||||
private ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
String sessionId = event.getSessionId();
|
||||
this.events.put(sessionId, event);
|
||||
Object lock = getLock(sessionId);
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
this.events.clear();
|
||||
this.locks.clear();
|
||||
}
|
||||
|
||||
boolean receivedEvent(String sessionId) throws InterruptedException {
|
||||
return waitForEvent(sessionId) != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<E extends AbstractSessionEvent> E getEvent(String sessionId) throws InterruptedException {
|
||||
return (E) waitForEvent(sessionId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends AbstractSessionEvent> E waitForEvent(String sessionId) throws InterruptedException {
|
||||
Object lock = getLock(sessionId);
|
||||
synchronized (lock) {
|
||||
if (!this.events.containsKey(sessionId)) {
|
||||
lock.wait(10000);
|
||||
}
|
||||
}
|
||||
return (E) this.events.get(sessionId);
|
||||
}
|
||||
|
||||
private Object getLock(String sessionId) {
|
||||
return this.locks.computeIfAbsent(sessionId, (k) -> new Object());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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 https://www.hazelcast.com/schema/config/hazelcast-config-4.0.xsd">
|
||||
|
||||
<network>
|
||||
<join>
|
||||
<multicast enabled="false"/>
|
||||
</join>
|
||||
</network>
|
||||
|
||||
<user-code-deployment enabled="true">
|
||||
<class-cache-mode>ETERNAL</class-cache-mode>
|
||||
<provider-mode>LOCAL_AND_CACHED_CLASSES</provider-mode>
|
||||
</user-code-deployment>
|
||||
|
||||
</hazelcast>
|
||||
@@ -0,0 +1 @@
|
||||
ryuk.container.timeout=120
|
||||
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import com.hazelcast.core.EntryEvent;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.map.IMap;
|
||||
import com.hazelcast.map.listener.EntryAddedListener;
|
||||
import com.hazelcast.map.listener.EntryEvictedListener;
|
||||
import com.hazelcast.map.listener.EntryExpiredListener;
|
||||
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.ApplicationEventPublisher;
|
||||
import org.springframework.session.DelegatingIndexResolver;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.PrincipalNameIndexResolver;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
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;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.session.SessionRepository} implementation using Hazelcast
|
||||
* 4 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);
|
||||
*
|
||||
* Hazelcast4IndexedSessionRepository sessionRepository =
|
||||
* new Hazelcast4IndexedSessionRepository(hazelcastInstance);
|
||||
* </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">
|
||||
* AttributeConfig attributeConfig = new AttributeConfig()
|
||||
* .setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
* .setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
|
||||
*
|
||||
* Config config = new Config();
|
||||
*
|
||||
* config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||
* .addAttributeConfig(attributeConfig)
|
||||
* .addIndexConfig(new IndexConfig(
|
||||
* IndexType.HASH,
|
||||
* Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||
*
|
||||
* 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>entryExpired - {@link SessionExpiredEvent}</li>
|
||||
* <li>entryRemoved - {@link SessionDeletedEvent}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class Hazelcast4IndexedSessionRepository
|
||||
implements FindByIndexNameSessionRepository<Hazelcast4IndexedSessionRepository.HazelcastSession>,
|
||||
EntryAddedListener<String, MapSession>, EntryEvictedListener<String, MapSession>,
|
||||
EntryRemovedListener<String, MapSession>, EntryExpiredListener<String, MapSession> {
|
||||
|
||||
/**
|
||||
* The default name of map used by Spring Session to store sessions.
|
||||
*/
|
||||
public static final String DEFAULT_SESSION_MAP_NAME = "spring:session:sessions";
|
||||
|
||||
/**
|
||||
* The principal name custom attribute name.
|
||||
*/
|
||||
public static final String PRINCIPAL_NAME_ATTRIBUTE = "principalName";
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private static final boolean SUPPORTS_SET_TTL = ClassUtils.hasAtLeastOneMethodWithName(IMap.class, "setTtl");
|
||||
|
||||
private static final Log logger = LogFactory.getLog(Hazelcast4IndexedSessionRepository.class);
|
||||
|
||||
private final HazelcastInstance hazelcastInstance;
|
||||
|
||||
private ApplicationEventPublisher eventPublisher = (event) -> {
|
||||
};
|
||||
|
||||
/**
|
||||
* If non-null, this value is used to override
|
||||
* {@link MapSession#setMaxInactiveInterval(Duration)}.
|
||||
*/
|
||||
private Integer defaultMaxInactiveInterval;
|
||||
|
||||
private IndexResolver<Session> indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
|
||||
|
||||
private String sessionMapName = DEFAULT_SESSION_MAP_NAME;
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private IMap<String, MapSession> sessions;
|
||||
|
||||
private UUID sessionListenerId;
|
||||
|
||||
/**
|
||||
* Create a new {@link Hazelcast4IndexedSessionRepository} instance.
|
||||
* @param hazelcastInstance the {@link HazelcastInstance} to use for managing sessions
|
||||
*/
|
||||
public Hazelcast4IndexedSessionRepository(HazelcastInstance hazelcastInstance) {
|
||||
Assert.notNull(hazelcastInstance, "HazelcastInstance must not be null");
|
||||
this.hazelcastInstance = hazelcastInstance;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.sessions = this.hazelcastInstance.getMap(this.sessionMapName);
|
||||
this.sessionListenerId = this.sessions.addEntryListener(this, true);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link IndexResolver} to use.
|
||||
* @param indexResolver the index resolver
|
||||
*/
|
||||
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||
Assert.notNull(indexResolver, "indexResolver cannot be null");
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of map used to store sessions.
|
||||
* @param sessionMapName the session map name
|
||||
*/
|
||||
public void setSessionMapName(String sessionMapName) {
|
||||
Assert.hasText(sessionMapName, "Map name must not be empty");
|
||||
this.sessionMapName = sessionMapName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Hazelcast flush mode. Default flush mode is {@link FlushMode#ON_SAVE}.
|
||||
* @param flushMode the new Hazelcast flush mode
|
||||
*/
|
||||
public void setFlushMode(FlushMode flushMode) {
|
||||
Assert.notNull(flushMode, "flushMode cannot be null");
|
||||
this.flushMode = flushMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the save mode.
|
||||
* @param saveMode the save mode
|
||||
*/
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
Assert.notNull(saveMode, "saveMode must not be null");
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HazelcastSession createSession() {
|
||||
MapSession cached = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
HazelcastSession session = new HazelcastSession(cached, true);
|
||||
session.flushImmediateIfNecessary();
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(HazelcastSession session) {
|
||||
if (session.isNew) {
|
||||
this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
else if (session.sessionIdChanged) {
|
||||
this.sessions.delete(session.originalId);
|
||||
session.originalId = session.getId();
|
||||
this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
else if (session.hasChanges()) {
|
||||
Hazelcast4SessionUpdateEntryProcessor entryProcessor = new Hazelcast4SessionUpdateEntryProcessor();
|
||||
if (session.lastAccessedTimeChanged) {
|
||||
entryProcessor.setLastAccessedTime(session.getLastAccessedTime());
|
||||
}
|
||||
if (session.maxInactiveIntervalChanged) {
|
||||
if (SUPPORTS_SET_TTL) {
|
||||
updateTtl(session);
|
||||
}
|
||||
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval());
|
||||
}
|
||||
if (!session.delta.isEmpty()) {
|
||||
entryProcessor.setDelta(new HashMap<>(session.delta));
|
||||
}
|
||||
this.sessions.executeOnKey(session.getId(), entryProcessor);
|
||||
}
|
||||
session.clearChangeFlags();
|
||||
}
|
||||
|
||||
private void updateTtl(HazelcastSession session) {
|
||||
this.sessions.setTtl(session.getId(), session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HazelcastSession findById(String id) {
|
||||
MapSession saved = this.sessions.get(id);
|
||||
if (saved == null) {
|
||||
return null;
|
||||
}
|
||||
if (saved.isExpired()) {
|
||||
deleteById(saved.getId());
|
||||
return null;
|
||||
}
|
||||
return new HazelcastSession(saved, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
this.sessions.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, HazelcastSession> 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, HazelcastSession> sessionMap = new HashMap<>(sessions.size());
|
||||
for (MapSession session : sessions) {
|
||||
sessionMap.put(session.getId(), new HazelcastSession(session, false));
|
||||
}
|
||||
return sessionMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryAdded(EntryEvent<String, MapSession> event) {
|
||||
MapSession session = event.getValue();
|
||||
if (session.getId().equals(session.getOriginalId())) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session created with id: " + session.getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionCreatedEvent(this, session));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryRemoved(EntryEvent<String, MapSession> event) {
|
||||
MapSession session = event.getOldValue();
|
||||
if (session != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session deleted with id: " + session.getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionDeletedEvent(this, session));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryExpired(EntryEvent<String, MapSession> event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session expired with id: " + event.getOldValue().getId());
|
||||
}
|
||||
this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
|
||||
* basis for its mapping. It keeps track if changes have been made since last save.
|
||||
*
|
||||
* @author Aleksandar Stojsavljevic
|
||||
*/
|
||||
final class HazelcastSession implements Session {
|
||||
|
||||
private final MapSession delegate;
|
||||
|
||||
private boolean isNew;
|
||||
|
||||
private boolean sessionIdChanged;
|
||||
|
||||
private boolean lastAccessedTimeChanged;
|
||||
|
||||
private boolean maxInactiveIntervalChanged;
|
||||
|
||||
private String originalId;
|
||||
|
||||
private Map<String, Object> delta = new HashMap<>();
|
||||
|
||||
HazelcastSession(MapSession cached, boolean isNew) {
|
||||
this.delegate = cached;
|
||||
this.isNew = isNew;
|
||||
this.originalId = cached.getId();
|
||||
if (this.isNew || (Hazelcast4IndexedSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
|
||||
getAttributeNames()
|
||||
.forEach((attributeName) -> this.delta.put(attributeName, cached.getAttribute(attributeName)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.delegate.setLastAccessedTime(lastAccessedTime);
|
||||
this.lastAccessedTimeChanged = true;
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return this.delegate.isExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getCreationTime() {
|
||||
return this.delegate.getCreationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.delegate.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String changeSessionId() {
|
||||
String newSessionId = this.delegate.changeSessionId();
|
||||
this.sessionIdChanged = true;
|
||||
return newSessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getLastAccessedTime() {
|
||||
return this.delegate.getLastAccessedTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(Duration interval) {
|
||||
this.delegate.setMaxInactiveInterval(interval);
|
||||
this.maxInactiveIntervalChanged = true;
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMaxInactiveInterval() {
|
||||
return this.delegate.getMaxInactiveInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
T attributeValue = this.delegate.getAttribute(attributeName);
|
||||
if (attributeValue != null
|
||||
&& Hazelcast4IndexedSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
|
||||
this.delta.put(attributeName, attributeValue);
|
||||
}
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNames() {
|
||||
return this.delegate.getAttributeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
this.delegate.setAttribute(attributeName, attributeValue);
|
||||
this.delta.put(attributeName, attributeValue);
|
||||
if (SPRING_SECURITY_CONTEXT.equals(attributeName)) {
|
||||
Map<String, String> indexes = Hazelcast4IndexedSessionRepository.this.indexResolver
|
||||
.resolveIndexesFor(this);
|
||||
String principal = (attributeValue != null) ? indexes.get(PRINCIPAL_NAME_INDEX_NAME) : null;
|
||||
this.delegate.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
}
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String attributeName) {
|
||||
setAttribute(attributeName, null);
|
||||
}
|
||||
|
||||
MapSession getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
boolean hasChanges() {
|
||||
return (this.lastAccessedTimeChanged || this.maxInactiveIntervalChanged || !this.delta.isEmpty());
|
||||
}
|
||||
|
||||
void clearChangeFlags() {
|
||||
this.isNew = false;
|
||||
this.lastAccessedTimeChanged = false;
|
||||
this.sessionIdChanged = false;
|
||||
this.maxInactiveIntervalChanged = false;
|
||||
this.delta.clear();
|
||||
}
|
||||
|
||||
private void flushImmediateIfNecessary() {
|
||||
if (Hazelcast4IndexedSessionRepository.this.flushMode == FlushMode.IMMEDIATE) {
|
||||
Hazelcast4IndexedSessionRepository.this.save(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
/**
|
||||
* Hazelcast {@link ValueExtractor} responsible for extracting principal name from the
|
||||
* {@link MapSession} to be used with Hazelcast 4.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class Hazelcast4PrincipalNameExtractor implements ValueExtractor<MapSession, String> {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void extract(MapSession target, String argument, ValueCollector collector) {
|
||||
String principalName = target.getAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
||||
if (principalName != null) {
|
||||
collector.addObject(principalName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import com.hazelcast.core.Offloadable;
|
||||
import com.hazelcast.map.EntryProcessor;
|
||||
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
/**
|
||||
* Hazelcast {@link EntryProcessor} responsible for handling updates to session when using
|
||||
* Hazelcast 4.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class Hazelcast4SessionUpdateEntryProcessor implements EntryProcessor<String, MapSession, Object>, Offloadable {
|
||||
|
||||
private Instant lastAccessedTime;
|
||||
|
||||
private Duration maxInactiveInterval;
|
||||
|
||||
private Map<String, Object> delta;
|
||||
|
||||
@Override
|
||||
public Object process(Map.Entry<String, MapSession> entry) {
|
||||
MapSession value = entry.getValue();
|
||||
if (value == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
if (this.lastAccessedTime != null) {
|
||||
value.setLastAccessedTime(this.lastAccessedTime);
|
||||
}
|
||||
if (this.maxInactiveInterval != null) {
|
||||
value.setMaxInactiveInterval(this.maxInactiveInterval);
|
||||
}
|
||||
if (this.delta != null) {
|
||||
for (final Map.Entry<String, Object> attribute : this.delta.entrySet()) {
|
||||
if (attribute.getValue() != null) {
|
||||
value.setAttribute(attribute.getKey(), attribute.getValue());
|
||||
}
|
||||
else {
|
||||
value.removeAttribute(attribute.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
entry.setValue(value);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutorName() {
|
||||
return OFFLOADABLE_EXECUTOR;
|
||||
}
|
||||
|
||||
void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.lastAccessedTime = lastAccessedTime;
|
||||
}
|
||||
|
||||
void setMaxInactiveInterval(Duration maxInactiveInterval) {
|
||||
this.maxInactiveInterval = maxInactiveInterval;
|
||||
}
|
||||
|
||||
void setDelta(Map<String, Object> delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.map.EntryProcessor;
|
||||
import com.hazelcast.map.IMap;
|
||||
import com.hazelcast.map.listener.MapListener;
|
||||
import com.hazelcast.query.impl.predicates.EqualPredicate;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
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.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.hazelcast.Hazelcast4IndexedSessionRepository.HazelcastSession;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link Hazelcast4IndexedSessionRepository}.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class Hazelcast4IndexedSessionRepositoryTests {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private IMap<String, MapSession> sessions = mock(IMap.class);
|
||||
|
||||
private Hazelcast4IndexedSessionRepository repository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
given(this.hazelcastInstance.<String, MapSession>getMap(anyString())).willReturn(this.sessions);
|
||||
this.repository = new Hazelcast4IndexedSessionRepository(this.hazelcastInstance);
|
||||
this.repository.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorNullHazelcastInstance() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new Hazelcast4IndexedSessionRepository(null))
|
||||
.withMessage("HazelcastInstance must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSaveModeNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSaveMode(null))
|
||||
.withMessage("saveMode must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionDefaultMaxInactiveInterval() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionCustomMaxInactiveInterval() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
int interval = 1;
|
||||
this.repository.setDefaultMaxInactiveInterval(interval);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveNewFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
verifyZeroInteractions(this.sessions);
|
||||
|
||||
this.repository.save(session);
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveNewFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedAttributeFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setAttribute("testName", "testValue");
|
||||
verifyZeroInteractions(this.sessions);
|
||||
|
||||
this.repository.save(session);
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedAttributeFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setAttribute("testName", "testValue");
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAttributeFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.removeAttribute("testName");
|
||||
verifyZeroInteractions(this.sessions);
|
||||
|
||||
this.repository.save(session);
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAttributeFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.removeAttribute("testName");
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedLastAccessedTimeFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
verifyZeroInteractions(this.sessions);
|
||||
|
||||
this.repository.save(session);
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedLastAccessedTimeFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedMaxInactiveIntervalInSecondsFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
|
||||
this.repository.save(session);
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUpdatedMaxInactiveIntervalInSecondsFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||
verify(this.sessions, times(1)).set(eq(sessionId), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
verify(this.sessions).setTtl(eq(sessionId), anyLong(), any());
|
||||
verify(this.sessions, times(1)).executeOnKey(eq(sessionId), any(EntryProcessor.class));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUnchangedFlushModeOnSave() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
this.repository.save(session);
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUnchangedFlushModeImmediate() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||
eq(TimeUnit.SECONDS));
|
||||
|
||||
this.repository.save(session);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSessionNotFound() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String sessionId = "testSessionId";
|
||||
|
||||
HazelcastSession session = this.repository.findById(sessionId);
|
||||
|
||||
assertThat(session).isNull();
|
||||
verify(this.sessions, times(1)).get(eq(sessionId));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSessionExpired() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
MapSession expired = new MapSession();
|
||||
expired.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
|
||||
given(this.sessions.get(eq(expired.getId()))).willReturn(expired);
|
||||
|
||||
HazelcastSession session = this.repository.findById(expired.getId());
|
||||
|
||||
assertThat(session).isNull();
|
||||
verify(this.sessions, times(1)).get(eq(expired.getId()));
|
||||
verify(this.sessions, times(1)).remove(eq(expired.getId()));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSessionFound() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
MapSession saved = new MapSession();
|
||||
saved.setAttribute("savedName", "savedValue");
|
||||
given(this.sessions.get(eq(saved.getId()))).willReturn(saved);
|
||||
|
||||
HazelcastSession session = this.repository.findById(saved.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(saved.getId());
|
||||
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
|
||||
verify(this.sessions, times(1)).get(eq(saved.getId()));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String sessionId = "testSessionId";
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
|
||||
verify(this.sessions, times(1)).remove(eq(sessionId));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByIndexNameAndIndexValueUnknownIndexName() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String indexValue = "testIndexValue";
|
||||
|
||||
Map<String, HazelcastSession> sessions = this.repository.findByIndexNameAndIndexValue("testIndexName",
|
||||
indexValue);
|
||||
|
||||
assertThat(sessions).isEmpty();
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String principal = "username";
|
||||
|
||||
Map<String, HazelcastSession> sessions = this.repository
|
||||
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
|
||||
assertThat(sessions).isEmpty();
|
||||
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByIndexNameAndIndexValuePrincipalIndexNameFound() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
|
||||
String principal = "username";
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "notused",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
List<MapSession> saved = new ArrayList<>(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, HazelcastSession> sessions = this.repository
|
||||
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
|
||||
assertThat(sessions).hasSize(2);
|
||||
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
void getAttributeNamesAndRemove() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeOnSetAttribute() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(1);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeOnGetAttribute() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(2);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeAlways() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ALWAYS);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(3);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,29 @@
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
configurations {
|
||||
hazelcast4
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-core')
|
||||
compile "com.hazelcast:hazelcast"
|
||||
compile "javax.annotation:javax.annotation-api"
|
||||
compile "org.springframework:spring-context"
|
||||
|
||||
hazelcast4(project(path: ":hazelcast4", configuration: 'classesOnlyElements'))
|
||||
compileOnly(project(":hazelcast4"))
|
||||
|
||||
testCompile "javax.servlet:javax.servlet-api"
|
||||
testCompile "org.springframework:spring-web"
|
||||
testCompile "org.springframework.security:spring-security-core"
|
||||
testCompile "org.junit.jupiter:junit-jupiter-api"
|
||||
testRuntime project(':hazelcast4')
|
||||
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
||||
|
||||
integrationTestCompile "com.hazelcast:hazelcast-client"
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
}
|
||||
|
||||
jar {
|
||||
from configurations.hazelcast4
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
||||
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
|
||||
/**
|
||||
@@ -68,7 +67,7 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Import(HazelcastHttpSessionConfiguration.class)
|
||||
@Import(EnableHazelcastHttpSessionSelector.class)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public @interface EnableHazelcastHttpSession {
|
||||
|
||||
@@ -81,11 +80,10 @@ public @interface EnableHazelcastHttpSession {
|
||||
|
||||
/**
|
||||
* This is the name of the Map that will be used in Hazelcast to store the session
|
||||
* data. Default is
|
||||
* {@link HazelcastIndexedSessionRepository#DEFAULT_SESSION_MAP_NAME}.
|
||||
* data. Default is "spring:session:sessions".
|
||||
* @return the name of the Map to store the sessions in Hazelcast
|
||||
*/
|
||||
String sessionMapName() default HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME;
|
||||
String sessionMapName() default "spring:session:sessions";
|
||||
|
||||
/**
|
||||
* Flush mode for the Hazelcast sessions. The default is {@code ON_SAVE} which only
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Dynamically determines which Hazelcast configuration class to include.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.4.0
|
||||
*/
|
||||
class EnableHazelcastHttpSessionSelector implements ImportSelector {
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
boolean hazelcast4OnClasspath = ClassUtils.isPresent("com.hazelcast.map.IMap", classLoader);
|
||||
List<String> classNames = new ArrayList<>(1);
|
||||
if (hazelcast4OnClasspath) {
|
||||
classNames.add(Hazelcast4HttpSessionConfiguration.class.getName());
|
||||
}
|
||||
else {
|
||||
classNames.add(HazelcastHttpSessionConfiguration.class.getName());
|
||||
}
|
||||
return classNames.toArray(new String[0]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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
|
||||
*
|
||||
* https://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 java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.hazelcast.Hazelcast4IndexedSessionRepository;
|
||||
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
||||
import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Exposes the {@link SessionRepositoryFilter} as a bean named
|
||||
* {@code springSessionRepositoryFilter}. In order to use this a single
|
||||
* {@link HazelcastInstance} configured for Hazelcast 4 must be exposed as a Bean.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 2.4.0
|
||||
* @see EnableHazelcastHttpSession
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class Hazelcast4HttpSessionConfiguration extends SpringHttpSessionConfiguration implements ImportAware {
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||
|
||||
private String sessionMapName = Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME;
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private HazelcastInstance hazelcastInstance;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private IndexResolver<Session> indexResolver;
|
||||
|
||||
private List<SessionRepositoryCustomizer<Hazelcast4IndexedSessionRepository>> sessionRepositoryCustomizers;
|
||||
|
||||
@Bean
|
||||
public Hazelcast4IndexedSessionRepository sessionRepository() {
|
||||
Hazelcast4IndexedSessionRepository sessionRepository = new Hazelcast4IndexedSessionRepository(
|
||||
this.hazelcastInstance);
|
||||
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
if (this.indexResolver != null) {
|
||||
sessionRepository.setIndexResolver(this.indexResolver);
|
||||
}
|
||||
if (StringUtils.hasText(this.sessionMapName)) {
|
||||
sessionRepository.setSessionMapName(this.sessionMapName);
|
||||
}
|
||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
sessionRepository.setFlushMode(this.flushMode);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
this.sessionRepositoryCustomizers
|
||||
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
public void setSessionMapName(String sessionMapName) {
|
||||
this.sessionMapName = sessionMapName;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setHazelcastFlushMode(HazelcastFlushMode hazelcastFlushMode) {
|
||||
setFlushMode(hazelcastFlushMode.getFlushMode());
|
||||
}
|
||||
|
||||
public void setFlushMode(FlushMode flushMode) {
|
||||
this.flushMode = flushMode;
|
||||
}
|
||||
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setHazelcastInstance(
|
||||
@SpringSessionHazelcastInstance ObjectProvider<HazelcastInstance> springSessionHazelcastInstance,
|
||||
ObjectProvider<HazelcastInstance> hazelcastInstance) {
|
||||
HazelcastInstance hazelcastInstanceToUse = springSessionHazelcastInstance.getIfAvailable();
|
||||
if (hazelcastInstanceToUse == null) {
|
||||
hazelcastInstanceToUse = hazelcastInstance.getObject();
|
||||
}
|
||||
this.hazelcastInstance = hazelcastInstanceToUse;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setSessionRepositoryCustomizer(
|
||||
ObjectProvider<SessionRepositoryCustomizer<Hazelcast4IndexedSessionRepository>> sessionRepositoryCustomizers) {
|
||||
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> attributeMap = importMetadata
|
||||
.getAnnotationAttributes(EnableHazelcastHttpSession.class.getName());
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
|
||||
this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds");
|
||||
String sessionMapNameValue = attributes.getString("sessionMapName");
|
||||
if (StringUtils.hasText(sessionMapNameValue)) {
|
||||
this.sessionMapName = sessionMapNameValue;
|
||||
}
|
||||
FlushMode flushMode = attributes.getEnum("flushMode");
|
||||
HazelcastFlushMode hazelcastFlushMode = attributes.getEnum("hazelcastFlushMode");
|
||||
if (flushMode == FlushMode.ON_SAVE && hazelcastFlushMode != HazelcastFlushMode.ON_SAVE) {
|
||||
flushMode = hazelcastFlushMode.getFlushMode();
|
||||
}
|
||||
this.flushMode = flushMode;
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user