diff --git a/spring-session-hazelcast/spring-session-hazelcast.gradle b/spring-session-hazelcast/spring-session-hazelcast.gradle index 8e036f01..ae4d5c5f 100644 --- a/spring-session-hazelcast/spring-session-hazelcast.gradle +++ b/spring-session-hazelcast/spring-session-hazelcast.gradle @@ -10,4 +10,5 @@ dependencies { testCompile "org.springframework.security:spring-security-core" integrationTestCompile "com.hazelcast:hazelcast-client" + integrationTestCompile "org.testcontainers:testcontainers" } diff --git a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java index 9c4c03b7..26b47e62 100644 --- a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java +++ b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/AbstractHazelcastRepositoryITests.java @@ -18,9 +18,17 @@ package org.springframework.session.hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; +import com.hazelcast.instance.HazelcastInstanceProxy; +import org.junit.Assume; import org.junit.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.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession; @@ -34,8 +42,10 @@ import static org.assertj.core.api.Assertions.assertThat; */ public abstract class AbstractHazelcastRepositoryITests { + private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; + @Autowired - private HazelcastInstance hazelcast; + private HazelcastInstance hazelcastInstance; @Autowired private HazelcastSessionRepository repository; @@ -45,7 +55,7 @@ public abstract class AbstractHazelcastRepositoryITests { HazelcastSession sessionToSave = this.repository.createSession(); String sessionId = sessionToSave.getId(); - IMap hazelcastMap = this.hazelcast + IMap hazelcastMap = this.hazelcastInstance .getMap(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME); assertThat(hazelcastMap.size()).isEqualTo(0); @@ -61,7 +71,7 @@ public abstract class AbstractHazelcastRepositoryITests { } @Test - public void changeSessionIdWhenOnlyChangeId() throws Exception { + public void changeSessionIdWhenOnlyChangeId() { String attrName = "changeSessionId"; String attrValue = "changeSessionId-value"; HazelcastSession toSave = this.repository.createSession(); @@ -90,7 +100,7 @@ public abstract class AbstractHazelcastRepositoryITests { } @Test - public void changeSessionIdWhenChangeTwice() throws Exception { + public void changeSessionIdWhenChangeTwice() { HazelcastSession toSave = this.repository.createSession(); this.repository.save(toSave); @@ -109,7 +119,7 @@ public abstract class AbstractHazelcastRepositoryITests { } @Test - public void changeSessionIdWhenSetAttributeOnChangedSession() throws Exception { + public void changeSessionIdWhenSetAttributeOnChangedSession() { String attrName = "changeSessionId"; String attrValue = "changeSessionId-value"; @@ -138,10 +148,7 @@ public abstract class AbstractHazelcastRepositoryITests { } @Test - public void changeSessionIdWhenHasNotSaved() throws Exception { - String attrName = "changeSessionId"; - String attrValue = "changeSessionId-value"; - + public void changeSessionIdWhenHasNotSaved() { HazelcastSession toSave = this.repository.createSession(); String originalId = toSave.getId(); toSave.changeSessionId(); @@ -167,4 +174,57 @@ public abstract class AbstractHazelcastRepositoryITests { assertThat(this.repository.findById(sessionId)).isNull(); } + @Test + public 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 + public 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(); + } + + @Test + public void createSessionWithSecurityContextAndFindByPrincipal() { + Assume.assumeTrue("Hazelcast runs in embedded server topology", + this.hazelcastInstance instanceof HazelcastInstanceProxy); + + HazelcastSession session = this.repository.createSession(); + + String username = "saves-" + System.currentTimeMillis(); + Authentication authentication = new UsernamePasswordAuthenticationToken(username, + "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.findByIndexNameAndIndexValue( + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username)) + .isNotNull(); + } + } diff --git a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastClientRepositoryITests.java b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastClientRepositoryITests.java index 0d375867..8aa823a1 100644 --- a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastClientRepositoryITests.java +++ b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastClientRepositoryITests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2018 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. @@ -22,14 +22,17 @@ import com.hazelcast.core.HazelcastInstance; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.runner.RunWith; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; 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.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; -import org.springframework.util.SocketUtils; /** * Integration tests that check the underlying data source - in this case Hazelcast @@ -44,20 +47,23 @@ import org.springframework.util.SocketUtils; @WebAppConfiguration public class HazelcastClientRepositoryITests extends AbstractHazelcastRepositoryITests { - private static final int PORT = SocketUtils.findAvailableTcpPort(); - - private static HazelcastInstance hazelcastInstance; + private static GenericContainer container = new GenericContainer<>( + "hazelcast/hazelcast:3.9.4") + .withExposedPorts(5701) + .withEnv("JAVA_OPTS", + "-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml") + .withClasspathResourceMapping("/hazelcast-server.xml", + "/opt/hazelcast/config_ext/hazelcast.xml", + BindMode.READ_ONLY); @BeforeClass - public static void setup() { - hazelcastInstance = HazelcastITestUtils.embeddedHazelcastServer(PORT); + public static void setUpClass() { + container.start(); } @AfterClass - public static void teardown() { - if (hazelcastInstance != null) { - hazelcastInstance.shutdown(); - } + public static void tearDownClass() { + container.stop(); } @Configuration @@ -65,9 +71,13 @@ public class HazelcastClientRepositoryITests extends AbstractHazelcastRepository static class HazelcastSessionConfig { @Bean - public HazelcastInstance embeddedHazelcastClient() { + public HazelcastInstance hazelcastInstance() { ClientConfig clientConfig = new ClientConfig(); - clientConfig.getNetworkConfig().addAddress("127.0.0.1:" + PORT); + clientConfig.getNetworkConfig().addAddress(container.getContainerIpAddress() + + ":" + container.getFirstMappedPort()); + clientConfig.getUserCodeDeploymentConfig().setEnabled(true) + .addClass(Session.class).addClass(MapSession.class) + .addClass(SessionUpdateEntryProcessor.class); return HazelcastClient.newHazelcastClient(clientConfig); } diff --git a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastServerRepositoryITests.java b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastServerRepositoryITests.java index 5f7b2e3e..f4a27e62 100644 --- a/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastServerRepositoryITests.java +++ b/spring-session-hazelcast/src/integration-test/java/org/springframework/session/hazelcast/HazelcastServerRepositoryITests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2018 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. @@ -43,7 +43,7 @@ public class HazelcastServerRepositoryITests extends AbstractHazelcastRepository static class HazelcastSessionConfig { @Bean - public HazelcastInstance embeddedHazelcastServer() { + public HazelcastInstance hazelcastInstance() { return HazelcastITestUtils.embeddedHazelcastServer(); } diff --git a/spring-session-hazelcast/src/integration-test/resources/hazelcast-server.xml b/spring-session-hazelcast/src/integration-test/resources/hazelcast-server.xml new file mode 100644 index 00000000..13ecceee --- /dev/null +++ b/spring-session-hazelcast/src/integration-test/resources/hazelcast-server.xml @@ -0,0 +1,11 @@ + + + + + ETERNAL + LOCAL_AND_CACHED_CLASSES + + + diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java index bbb51c3d..1c54c0a2 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionRepository.java @@ -31,8 +31,6 @@ import javax.annotation.PreDestroy; import com.hazelcast.core.EntryEvent; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; -import com.hazelcast.map.AbstractEntryProcessor; -import com.hazelcast.map.EntryProcessor; import com.hazelcast.map.listener.EntryAddedListener; import com.hazelcast.map.listener.EntryEvictedListener; import com.hazelcast.map.listener.EntryRemovedListener; @@ -452,59 +450,4 @@ public class HazelcastSessionRepository implements } - /** - * Hazelcast {@link EntryProcessor} responsible for handling updates to session. - * - * @since 2.0.0 - * @see #save(HazelcastSession) - */ - private static final class SessionUpdateEntryProcessor - extends AbstractEntryProcessor { - - private Instant lastAccessedTime; - - private Duration maxInactiveInterval; - - private Map delta; - - @Override - public Object process(Map.Entry 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 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; - } - - public void setLastAccessedTime(Instant lastAccessedTime) { - this.lastAccessedTime = lastAccessedTime; - } - - public void setMaxInactiveInterval(Duration maxInactiveInterval) { - this.maxInactiveInterval = maxInactiveInterval; - } - - public void setDelta(Map delta) { - this.delta = delta; - } - - } - } diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/SessionUpdateEntryProcessor.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/SessionUpdateEntryProcessor.java new file mode 100644 index 00000000..3b1332ed --- /dev/null +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/SessionUpdateEntryProcessor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2014-2018 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.time.Duration; +import java.time.Instant; +import java.util.Map; + +import com.hazelcast.map.AbstractEntryProcessor; +import com.hazelcast.map.EntryProcessor; + +import org.springframework.session.MapSession; + +/** + * Hazelcast {@link EntryProcessor} responsible for handling updates to session. + * + * @author Vedran Pavic + * @since 2.0.5 + * @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession) + */ +class SessionUpdateEntryProcessor extends AbstractEntryProcessor { + + private Instant lastAccessedTime; + + private Duration maxInactiveInterval; + + private Map delta; + + @Override + public Object process(Map.Entry 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 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; + } + + void setLastAccessedTime(Instant lastAccessedTime) { + this.lastAccessedTime = lastAccessedTime; + } + + void setMaxInactiveInterval(Duration maxInactiveInterval) { + this.maxInactiveInterval = maxInactiveInterval; + } + + void setDelta(Map delta) { + this.delta = delta; + } + +}