From 34f29cf36c7358595d7aab354977898646cc4627 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Mon, 2 Oct 2017 12:52:52 +0200 Subject: [PATCH] Improve Hazelcast configuration This commit improves Hazelcast configuration by introducing `@SpringSessionHazelcastInstance` qualifier for explicitly declaring a `HazelcastInstance` to be used by Spring Session. This is in particular useful in scenarios with multiple `HazelcastInstance` beans present in the application context. Closes gh-912 --- .../SpringSessionHazelcastInstance.java | 44 ++++ .../HazelcastHttpSessionConfiguration.java | 20 +- ...azelcastHttpSessionConfigurationTests.java | 210 +++++++++++++++--- 3 files changed, 241 insertions(+), 33 deletions(-) create mode 100644 spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/SpringSessionHazelcastInstance.java diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/SpringSessionHazelcastInstance.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/SpringSessionHazelcastInstance.java new file mode 100644 index 00000000..98cb4c70 --- /dev/null +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/SpringSessionHazelcastInstance.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.hazelcast.config.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.hazelcast.core.HazelcastInstance; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.session.hazelcast.HazelcastSessionRepository; + +/** + * Qualifier annotation for a {@link HazelcastInstance} to be injected in + * {@link HazelcastSessionRepository}. + * + * @author Vedran Pavic + * @since 2.0.0 + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, + ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier +public @interface SpringSessionHazelcastInstance { + +} diff --git a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java index 5e61e56c..68e60d28 100644 --- a/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java +++ b/spring-session-hazelcast/src/main/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -21,6 +21,7 @@ import java.util.Map; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,6 +32,7 @@ import org.springframework.session.MapSession; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; import org.springframework.session.hazelcast.HazelcastFlushMode; import org.springframework.session.hazelcast.HazelcastSessionRepository; +import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance; import org.springframework.session.web.http.SessionRepositoryFilter; /** @@ -57,15 +59,21 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur @Bean public HazelcastSessionRepository sessionRepository( - HazelcastInstance hazelcastInstance, + @SpringSessionHazelcastInstance ObjectProvider springSessionHazelcastInstance, + ObjectProvider hazelcastInstance, ApplicationEventPublisher eventPublisher) { - IMap sessions = hazelcastInstance.getMap( - this.sessionMapName); + HazelcastInstance hazelcastInstanceToUse = springSessionHazelcastInstance + .getIfAvailable(); + if (hazelcastInstanceToUse == null) { + hazelcastInstanceToUse = hazelcastInstance.getObject(); + } + IMap sessions = hazelcastInstanceToUse + .getMap(this.sessionMapName); HazelcastSessionRepository sessionRepository = new HazelcastSessionRepository( sessions); sessionRepository.setApplicationEventPublisher(eventPublisher); - sessionRepository.setDefaultMaxInactiveInterval( - this.maxInactiveIntervalInSeconds); + sessionRepository + .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); sessionRepository.setHazelcastFlushMode(this.hazelcastFlushMode); return sessionRepository; } diff --git a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java index bb04204e..eac49839 100644 --- a/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java +++ b/spring-session-hazelcast/src/test/java/org/springframework/session/hazelcast/config/annotation/web/http/HazelcastHttpSessionConfigurationTests.java @@ -19,25 +19,24 @@ package org.springframework.session.hazelcast.config.annotation.web.http; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.session.hazelcast.HazelcastFlushMode; import org.springframework.session.hazelcast.HazelcastSessionRepository; +import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; /** * Tests for {@link HazelcastHttpSessionConfiguration}. @@ -45,7 +44,6 @@ import static org.mockito.BDDMockito.given; * @author Vedran Pavic * @author Aleksandar Stojsavljevic */ -@RunWith(MockitoJUnitRunner.class) public class HazelcastHttpSessionConfigurationTests { private static final String MAP_NAME = "spring:test:sessions"; @@ -57,19 +55,8 @@ public class HazelcastHttpSessionConfigurationTests { @Rule public final ExpectedException thrown = ExpectedException.none(); - @Mock - private static HazelcastInstance hazelcastInstance; - - @Mock - private IMap sessions; - private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - @Before - public void setUp() { - given(hazelcastInstance.getMap(isA(String.class))).willReturn(this.sessions); - } - @After public void closeContext() { if (this.context != null) { @@ -79,18 +66,17 @@ public class HazelcastHttpSessionConfigurationTests { @Test public void noHazelcastInstanceConfiguration() { - this.thrown.expect(UnsatisfiedDependencyException.class); + this.thrown.expect(BeanCreationException.class); this.thrown.expectMessage("HazelcastInstance"); - registerAndRefresh(EmptyConfiguration.class); + registerAndRefresh(NoHazelcastInstanceConfiguration.class); } @Test public void defaultConfiguration() { registerAndRefresh(DefaultConfiguration.class); - assertThat(this.context.getBean(HazelcastSessionRepository.class)) - .isNotNull(); + assertThat(this.context.getBean(HazelcastSessionRepository.class)).isNotNull(); } @Test @@ -128,8 +114,8 @@ public class HazelcastHttpSessionConfigurationTests { HazelcastSessionRepository repository = this.context .getBean(HazelcastSessionRepository.class); assertThat(repository).isNotNull(); - assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval")).isEqualTo( - MAX_INACTIVE_INTERVAL_IN_SECONDS); + assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval")) + .isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); } @Test @@ -166,6 +152,70 @@ public class HazelcastHttpSessionConfigurationTests { .isEqualTo(HazelcastFlushMode.IMMEDIATE); } + @Test + public void qualifiedHazelcastInstanceConfiguration() { + registerAndRefresh(QualifiedHazelcastInstanceConfiguration.class); + + HazelcastSessionRepository repository = this.context + .getBean(HazelcastSessionRepository.class); + HazelcastInstance hazelcastInstance = this.context + .getBean("qualifiedHazelcastInstance", HazelcastInstance.class); + assertThat(repository).isNotNull(); + assertThat(hazelcastInstance).isNotNull(); + assertThat(ReflectionTestUtils.getField(repository, "sessions")).isEqualTo( + QualifiedHazelcastInstanceConfiguration.qualifiedHazelcastInstanceSessions); + } + + @Test + public void primaryHazelcastInstanceConfiguration() { + registerAndRefresh(PrimaryHazelcastInstanceConfiguration.class); + + HazelcastSessionRepository repository = this.context + .getBean(HazelcastSessionRepository.class); + HazelcastInstance hazelcastInstance = this.context + .getBean("primaryHazelcastInstance", HazelcastInstance.class); + assertThat(repository).isNotNull(); + assertThat(hazelcastInstance).isNotNull(); + assertThat(ReflectionTestUtils.getField(repository, "sessions")).isEqualTo( + PrimaryHazelcastInstanceConfiguration.primaryHazelcastInstanceSessions); + } + + @Test + public void qualifiedAndPrimaryHazelcastInstanceConfiguration() { + registerAndRefresh(QualifiedAndPrimaryHazelcastInstanceConfiguration.class); + + HazelcastSessionRepository repository = this.context + .getBean(HazelcastSessionRepository.class); + HazelcastInstance hazelcastInstance = this.context + .getBean("qualifiedHazelcastInstance", HazelcastInstance.class); + assertThat(repository).isNotNull(); + assertThat(hazelcastInstance).isNotNull(); + assertThat(ReflectionTestUtils.getField(repository, "sessions")).isEqualTo( + QualifiedAndPrimaryHazelcastInstanceConfiguration.qualifiedHazelcastInstanceSessions); + } + + @Test + public void namedDataSourceConfiguration() { + registerAndRefresh(NamedHazelcastInstanceConfiguration.class); + + HazelcastSessionRepository repository = this.context + .getBean(HazelcastSessionRepository.class); + HazelcastInstance hazelcastInstance = this.context.getBean("hazelcastInstance", + HazelcastInstance.class); + assertThat(repository).isNotNull(); + assertThat(hazelcastInstance).isNotNull(); + assertThat(ReflectionTestUtils.getField(repository, "sessions")).isEqualTo( + NamedHazelcastInstanceConfiguration.hazelcastInstanceSessions); + } + + @Test + public void multipleDataSourceConfiguration() { + this.thrown.expect(BeanCreationException.class); + this.thrown.expectMessage("sessionRepository"); + + registerAndRefresh(MultipleHazelcastInstanceConfiguration.class); + } + private void registerAndRefresh(Class... annotatedClasses) { this.context.register(annotatedClasses); this.context.refresh(); @@ -173,13 +223,19 @@ public class HazelcastHttpSessionConfigurationTests { @Configuration @EnableHazelcastHttpSession - static class EmptyConfiguration { + static class NoHazelcastInstanceConfiguration { } static class BaseConfiguration { + @SuppressWarnings("unchecked") + static IMap defaultHazelcastInstanceSessions = mock(IMap.class); + @Bean - public HazelcastInstance hazelcastInstance() { + public HazelcastInstance defaultHazelcastInstance() { + HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class); + given(hazelcastInstance.getMap(anyString())) + .willReturn(defaultHazelcastInstanceSessions); return hazelcastInstance; } @@ -233,8 +289,108 @@ public class HazelcastHttpSessionConfigurationTests { @Configuration @EnableHazelcastHttpSession(hazelcastFlushMode = HazelcastFlushMode.IMMEDIATE) - static class CustomFlushImmediatelyConfiguration + static class CustomFlushImmediatelyConfiguration extends BaseConfiguration { + } + + @Configuration + @EnableHazelcastHttpSession + static class QualifiedHazelcastInstanceConfiguration extends BaseConfiguration { + + @SuppressWarnings("unchecked") + static IMap qualifiedHazelcastInstanceSessions = mock(IMap.class); + + @Bean + @SpringSessionHazelcastInstance + public HazelcastInstance qualifiedHazelcastInstance() { + HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class); + given(hazelcastInstance.getMap(anyString())) + .willReturn(qualifiedHazelcastInstanceSessions); + return hazelcastInstance; + } + + } + + @Configuration + @EnableHazelcastHttpSession + static class PrimaryHazelcastInstanceConfiguration extends BaseConfiguration { + + @SuppressWarnings("unchecked") + static IMap primaryHazelcastInstanceSessions = mock(IMap.class); + + @Bean + @Primary + public HazelcastInstance primaryHazelcastInstance() { + HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class); + given(hazelcastInstance.getMap(anyString())) + .willReturn(primaryHazelcastInstanceSessions); + return hazelcastInstance; + } + + } + + @Configuration + @EnableHazelcastHttpSession + static class QualifiedAndPrimaryHazelcastInstanceConfiguration extends BaseConfiguration { + + @SuppressWarnings("unchecked") + static IMap qualifiedHazelcastInstanceSessions = mock(IMap.class); + + @SuppressWarnings("unchecked") + static IMap primaryHazelcastInstanceSessions = mock(IMap.class); + + @Bean + @SpringSessionHazelcastInstance + public HazelcastInstance qualifiedHazelcastInstance() { + HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class); + given(hazelcastInstance.getMap(anyString())) + .willReturn(qualifiedHazelcastInstanceSessions); + return hazelcastInstance; + } + + @Bean + @Primary + public HazelcastInstance primaryHazelcastInstance() { + HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class); + given(hazelcastInstance.getMap(anyString())) + .willReturn(primaryHazelcastInstanceSessions); + return hazelcastInstance; + } + + } + + @Configuration + @EnableHazelcastHttpSession + static class NamedHazelcastInstanceConfiguration extends BaseConfiguration { + + @SuppressWarnings("unchecked") + static IMap hazelcastInstanceSessions = mock(IMap.class); + + @Bean + public HazelcastInstance hazelcastInstance() { + HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class); + given(hazelcastInstance.getMap(anyString())) + .willReturn(hazelcastInstanceSessions); + return hazelcastInstance; + } + + } + + @Configuration + @EnableHazelcastHttpSession + static class MultipleHazelcastInstanceConfiguration extends BaseConfiguration { + + @SuppressWarnings("unchecked") + static IMap secondaryHazelcastInstanceSessions = mock(IMap.class); + + @Bean + public HazelcastInstance secondaryHazelcastInstance() { + HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class); + given(hazelcastInstance.getMap(anyString())) + .willReturn(secondaryHazelcastInstanceSessions); + return hazelcastInstance; + } + } }