Add Hazelcast
Fixes gh-276
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.data;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
|
||||
public class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||
private AbstractSessionEvent event;
|
||||
private Object lock = new Object();
|
||||
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
this.event = event;
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void setLock(Object lock) {
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.event = null;
|
||||
}
|
||||
|
||||
public boolean receivedEvent() throws InterruptedException {
|
||||
return waitForEvent() != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends AbstractSessionEvent> E getEvent() throws InterruptedException {
|
||||
return (E) waitForEvent();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends AbstractSessionEvent> E waitForEvent() throws InterruptedException {
|
||||
synchronized(lock) {
|
||||
if(event == null) {
|
||||
lock.wait(3000);
|
||||
}
|
||||
}
|
||||
return (E) event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.data.hazelcast;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.data.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
|
||||
/**
|
||||
* Integration tests that check the underlying data source - in this case
|
||||
* Hazelcast.
|
||||
*
|
||||
* @author Tommy Ludwig
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class HazelcastRepositoryITests<S extends ExpiringSession> {
|
||||
|
||||
@Autowired
|
||||
private HazelcastInstance hazelcast;
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@Test
|
||||
public void createAndDestorySession() {
|
||||
S sessionToSave = repository.createSession();
|
||||
String sessionId = sessionToSave.getId();
|
||||
|
||||
IMap<String, S> hazelcastMap = hazelcast.getMap("spring:session:sessions");
|
||||
|
||||
assertThat(hazelcastMap.size()).isEqualTo(0);
|
||||
|
||||
repository.save(sessionToSave);
|
||||
|
||||
assertThat(hazelcastMap.size()).isEqualTo(1);
|
||||
assertThat(hazelcastMap.get(sessionId)).isEqualTo(sessionToSave);
|
||||
|
||||
repository.delete(sessionId);
|
||||
|
||||
assertThat(hazelcastMap.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@EnableHazelcastHttpSession
|
||||
@Configuration
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance embeddedHazelcast() {
|
||||
Config hazelcastConfig = new Config();
|
||||
NetworkConfig netConfig = new NetworkConfig();
|
||||
netConfig.setPort(SocketUtils.findAvailableTcpPort());
|
||||
hazelcastConfig.setNetworkConfig(netConfig);
|
||||
return Hazelcast.newHazelcastInstance(hazelcastConfig);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.data.hazelcast.config.annotation.web.http;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
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.ExpiringSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.data.SessionEventRegistry;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
/**
|
||||
* 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 Tommy Ludwig
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class EnableHazelcastHttpSessionEventsTests<S extends ExpiringSession> {
|
||||
|
||||
private final static String MAX_INACTIVE_INTERVAL_IN_SECONDS_STR = "1";
|
||||
private final static int MAX_INACTIVE_INTERVAL_IN_SECONDS = Integer.valueOf(MAX_INACTIVE_INTERVAL_IN_SECONDS_STR);
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@Autowired
|
||||
private SessionEventRegistry registry;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
registry.clear();
|
||||
registry.setLock(lock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveSessionTest() throws InterruptedException {
|
||||
String username = "saves-"+System.currentTimeMillis();
|
||||
|
||||
S sessionToSave = 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(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME, username);
|
||||
|
||||
repository.save(sessionToSave);
|
||||
|
||||
assertThat(registry.receivedEvent()).isTrue();
|
||||
assertThat(registry.getEvent()).isInstanceOf(SessionCreatedEvent.class);
|
||||
|
||||
Session session = repository.getSession(sessionToSave.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(sessionToSave.getId());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(sessionToSave.getAttributeNames());
|
||||
assertThat(session.getAttribute(expectedAttributeName)).isEqualTo(sessionToSave.getAttribute(expectedAttributeName));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expiredSessionTest() throws InterruptedException {
|
||||
S sessionToSave = repository.createSession();
|
||||
|
||||
repository.save(sessionToSave);
|
||||
|
||||
assertThat(registry.receivedEvent()).isTrue();
|
||||
assertThat(registry.getEvent()).isInstanceOf(SessionCreatedEvent.class);
|
||||
registry.clear();
|
||||
|
||||
assertThat(sessionToSave.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
|
||||
synchronized (lock) {
|
||||
lock.wait((sessionToSave.getMaxInactiveIntervalInSeconds() * 1000) + 1);
|
||||
}
|
||||
|
||||
assertThat(registry.receivedEvent()).isTrue();
|
||||
assertThat(registry.getEvent()).isInstanceOf(SessionExpiredEvent.class);
|
||||
|
||||
assertThat(repository.getSession(sessionToSave.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletedSessionTest() throws InterruptedException {
|
||||
S sessionToSave = repository.createSession();
|
||||
|
||||
repository.save(sessionToSave);
|
||||
|
||||
assertThat(registry.receivedEvent()).isTrue();
|
||||
assertThat(registry.getEvent()).isInstanceOf(SessionCreatedEvent.class);
|
||||
registry.clear();
|
||||
|
||||
repository.delete(sessionToSave.getId());
|
||||
|
||||
assertThat(registry.receivedEvent()).isTrue();
|
||||
assertThat(registry.getEvent()).isInstanceOf(SessionDeletedEvent.class);
|
||||
|
||||
assertThat(repository.getSession(sessionToSave.getId())).isNull();
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS_STR)
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance embeddedHazelcast() {
|
||||
Config hazelcastConfig = new Config();
|
||||
NetworkConfig netConfig = new NetworkConfig();
|
||||
netConfig.setPort(SocketUtils.findAvailableTcpPort());
|
||||
hazelcastConfig.setNetworkConfig(netConfig);
|
||||
return Hazelcast.newHazelcastInstance(hazelcastConfig);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionEventRegistry sessionEventRegistry() {
|
||||
return new SessionEventRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
@@ -35,9 +34,9 @@ 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.Session;
|
||||
import org.springframework.session.data.SessionEventRegistry;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
@@ -88,7 +87,7 @@ public class RedisOperationsSessionRepositoryITests {
|
||||
Session session = repository.getSession(toSave.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(toSave.getId());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(session.getAttributeNames());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
|
||||
assertThat(session.getAttribute(expectedAttributeName)).isEqualTo(toSave.getAttribute(expectedAttributeName));
|
||||
|
||||
registry.clear();
|
||||
@@ -146,41 +145,6 @@ public class RedisOperationsSessionRepositoryITests {
|
||||
assertThat(findByPrincipalName.keySet()).excludes(toSave.getId());
|
||||
}
|
||||
|
||||
static class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||
private AbstractSessionEvent event;
|
||||
private final Object lock = new Object();
|
||||
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
this.event = event;
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.event = null;
|
||||
}
|
||||
|
||||
public boolean receivedEvent() throws InterruptedException {
|
||||
return waitForEvent() != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends AbstractSessionEvent> E getEvent() throws InterruptedException {
|
||||
return (E) waitForEvent();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends AbstractSessionEvent> E waitForEvent() throws InterruptedException {
|
||||
synchronized(lock) {
|
||||
if(event == null) {
|
||||
lock.wait(3000);
|
||||
}
|
||||
}
|
||||
return (E) event;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisOperationsSessionRepositoryITests")
|
||||
static class Config {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.data.hazelcast;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.hazelcast.core.EntryEvent;
|
||||
import com.hazelcast.map.listener.EntryAddedListener;
|
||||
import com.hazelcast.map.listener.EntryEvictedListener;
|
||||
import com.hazelcast.map.listener.EntryRemovedListener;
|
||||
|
||||
/**
|
||||
* Listen for events on the Hazelcast-backed SessionRepository and
|
||||
* translate those events into the corresponding Spring Session events.
|
||||
* Publish the Spring Session events with the given {@link ApplicationEventPublisher}.
|
||||
* <ul>
|
||||
* <li>entryAdded --> {@link SessionCreatedEvent}</li>
|
||||
* <li>entryEvicted --> {@link SessionExpiredEvent}</li>
|
||||
* <li>entryRemoved --> {@link SessionDeletedEvent}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Tommy Ludwig
|
||||
* @author Mark Anderson
|
||||
* @since 1.1
|
||||
*/
|
||||
public class SessionEntryListener implements EntryAddedListener<String, ExpiringSession>,
|
||||
EntryEvictedListener<String, ExpiringSession>, EntryRemovedListener<String, ExpiringSession> {
|
||||
private static final Log logger = LogFactory.getLog(SessionEntryListener.class);
|
||||
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
public SessionEntryListener(ApplicationEventPublisher eventPublisher) {
|
||||
Assert.notNull(eventPublisher, "eventPublisher cannot be null");
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
public void entryAdded(EntryEvent<String, ExpiringSession> event) {
|
||||
logger.debug("Session created with id: " + event.getValue().getId());
|
||||
this.eventPublisher.publishEvent(new SessionCreatedEvent(this, event.getValue()));
|
||||
}
|
||||
|
||||
public void entryEvicted(EntryEvent<String, ExpiringSession> event) {
|
||||
logger.debug("Session expired with id: " + event.getOldValue().getId());
|
||||
this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
|
||||
}
|
||||
|
||||
public void entryRemoved(EntryEvent<String, ExpiringSession> event) {
|
||||
logger.debug("Session deleted with id: " + event.getOldValue().getId());
|
||||
this.eventPublisher.publishEvent(new SessionDeletedEvent(this, event.getOldValue()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.data.hazelcast.config.annotation.web.http;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
|
||||
/**
|
||||
* Add this annotation to a {@code @Configuration} class to expose the
|
||||
* SessionRepositoryFilter as a bean named "springSessionRepositoryFilter" and
|
||||
* backed by Hazelcast. In order to leverage the annotation, a single {@link HazelcastInstance}
|
||||
* must be provided. For example:
|
||||
* <pre>
|
||||
* <code>
|
||||
* {@literal @Configuration}
|
||||
* {@literal @EnableHazelcastHttpSession}
|
||||
* public class HazelcastHttpSessionConfig {
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public HazelcastInstance embeddedHazelcast() {
|
||||
* Config hazelcastConfig = new Config();
|
||||
* return Hazelcast.newHazelcastInstance(hazelcastConfig);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* More advanced configurations can extend {@link HazelcastHttpSessionConfiguration} instead.
|
||||
*
|
||||
* @author Tommy Ludwig
|
||||
* @since 1.1
|
||||
* @see EnableSpringHttpSession
|
||||
*/
|
||||
@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||
@Target(value={java.lang.annotation.ElementType.TYPE})
|
||||
@Documented
|
||||
@Import(HazelcastHttpSessionConfiguration.class)
|
||||
@Configuration
|
||||
public @interface EnableHazelcastHttpSession {
|
||||
/**
|
||||
* This is the session timeout in seconds. By default, it is set to 1800 seconds (30 minutes).
|
||||
* This should be a non-negative integer.
|
||||
* <p>If you wish to use external configuration (outside of this annotation) to set this value, you can
|
||||
* set this to "" (an empty String), which will prevent this configuration from overriding
|
||||
* the external configuration for this value.</p>
|
||||
*
|
||||
* @return the seconds a session can be inactive before expiring
|
||||
*/
|
||||
String maxInactiveIntervalInSeconds() default "1800";
|
||||
|
||||
/**
|
||||
* This is the name of the Map that will be used in Hazelcast to store the session data.
|
||||
* Default is "spring:session:sessions".
|
||||
* @return the name of the Map to store the sessions in Hazelcast
|
||||
*/
|
||||
String sessionMapName() default "spring:session:sessions";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.data.hazelcast.config.annotation.web.http;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
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.annotation.AnnotationUtils;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.MapSessionRepository;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.data.hazelcast.SessionEntryListener;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.hazelcast.config.MapConfig;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
|
||||
/**
|
||||
* Exposes the {@link SessionRepositoryFilter} as a bean named
|
||||
* "springSessionRepositoryFilter". In order to use this a single
|
||||
* {@link HazelcastInstance} must be exposed as a Bean.
|
||||
*
|
||||
* @author Tommy Ludwig
|
||||
* @since 1.1
|
||||
* @see EnableHazelcastHttpSession
|
||||
*/
|
||||
@Configuration
|
||||
public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfiguration implements ImportAware, BeanClassLoaderAware {
|
||||
|
||||
/** This is the magic value to use if you do not want this configuration
|
||||
* overriding the maxIdleSeconds value for the Map backing the session data. */
|
||||
private static final String DO_NOT_CONFIGURE_INACTIVE_INTERVAL_STRING = "";
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = 1800;
|
||||
|
||||
private String sessionMapName = "spring:session:sessions";
|
||||
|
||||
private String sessionListenerUid;
|
||||
|
||||
private IMap<String, ExpiringSession> sessionsMap;
|
||||
|
||||
@Bean
|
||||
public SessionRepository<ExpiringSession> sessionRepository(HazelcastInstance hazelcastInstance, SessionEntryListener sessionListener) {
|
||||
configureSessionMap(hazelcastInstance);
|
||||
this.sessionsMap = hazelcastInstance.getMap(sessionMapName);
|
||||
this.sessionListenerUid = this.sessionsMap.addEntryListener(sessionListener, true);
|
||||
|
||||
MapSessionRepository sessionRepository = new MapSessionRepository(this.sessionsMap);
|
||||
sessionRepository.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
|
||||
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void removeSessionListener() {
|
||||
this.sessionsMap.removeEntryListener(this.sessionListenerUid);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionEntryListener sessionListener(ApplicationEventPublisher eventPublisher) {
|
||||
return new SessionEntryListener(eventPublisher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a {@link MapConfig} for the given sessionMapName if one does not exist.
|
||||
* Ensure that maxIdleSeconds is set to maxInactiveIntervalInSeconds for proper session expiration.
|
||||
*
|
||||
* @param hazelcastInstance the {@link HazelcastInstance} to configure
|
||||
*/
|
||||
private void configureSessionMap(HazelcastInstance hazelcastInstance) {
|
||||
MapConfig sessionMapConfig = hazelcastInstance.getConfig().getMapConfig(sessionMapName);
|
||||
if (this.maxInactiveIntervalInSeconds != null) {
|
||||
sessionMapConfig.setMaxIdleSeconds(this.maxInactiveIntervalInSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> enableAttrMap = importMetadata.getAnnotationAttributes(EnableHazelcastHttpSession.class.getName());
|
||||
AnnotationAttributes enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);
|
||||
if (enableAttrs == null) {
|
||||
// search parent classes
|
||||
Class<?> currentClass = ClassUtils.resolveClassName(importMetadata.getClassName(), beanClassLoader);
|
||||
for (Class<?> classToInspect = currentClass; classToInspect != null; classToInspect = classToInspect.getSuperclass()) {
|
||||
EnableHazelcastHttpSession enableHazelcastHttpSessionAnnotation = AnnotationUtils.findAnnotation(classToInspect, EnableHazelcastHttpSession.class);
|
||||
if (enableHazelcastHttpSessionAnnotation == null) {
|
||||
continue;
|
||||
}
|
||||
enableAttrMap = AnnotationUtils
|
||||
.getAnnotationAttributes(enableHazelcastHttpSessionAnnotation);
|
||||
enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);
|
||||
}
|
||||
}
|
||||
|
||||
transferAnnotationAttributes(enableAttrs);
|
||||
}
|
||||
|
||||
private void transferAnnotationAttributes(AnnotationAttributes enableAttrs) {
|
||||
String maxInactiveIntervalString = enableAttrs.getString("maxInactiveIntervalInSeconds");
|
||||
|
||||
if (DO_NOT_CONFIGURE_INACTIVE_INTERVAL_STRING.equals(maxInactiveIntervalString)) {
|
||||
this.maxInactiveIntervalInSeconds = null;
|
||||
} else {
|
||||
try {
|
||||
this.maxInactiveIntervalInSeconds = Integer.parseInt(maxInactiveIntervalString);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IllegalArgumentException(
|
||||
"@EnableHazelcastHttpSession's maxInactiveIntervalInSeconds expects an int format String but was '"
|
||||
+ maxInactiveIntervalString + "' instead.", nfe);
|
||||
}
|
||||
}
|
||||
this.sessionMapName = enableAttrs.getString("sessionMapName");
|
||||
}
|
||||
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
public String getSessionMapName() {
|
||||
return this.sessionMapName;
|
||||
}
|
||||
|
||||
public void setSessionMapName(String sessionMapName) {
|
||||
this.sessionMapName = sessionMapName;
|
||||
}
|
||||
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user