Add MapReactorSessionRepository

Fixes gh-815
This commit is contained in:
Rob Winch
2017-06-27 12:52:09 -05:00
parent db9807d12b
commit d42a7b65ea
2 changed files with 298 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
/*
* 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;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* A {@link SessionRepository} backed by a {@link Map} and that uses a
* {@link MapSession}. By default a {@link ConcurrentHashMap} is
* used, but a custom {@link Map} can be injected to use distributed maps
* provided by NoSQL stores like Redis and Hazelcast.
*
* <p>
* The implementation does NOT support firing {@link SessionDeletedEvent} or
* {@link SessionExpiredEvent}.
* </p>
*
* @author Rob Winch
* @since 2.0
*/
public class MapReactorSessionRepository implements ReactorSessionRepository<Session> {
/**
* If non-null, this value is used to override
* {@link Session#setMaxInactiveInterval(Duration)}.
*/
private Integer defaultMaxInactiveInterval;
private final Map<String, Session> sessions;
/**
* Creates an instance backed by a {@link ConcurrentHashMap}.
*/
public MapReactorSessionRepository() {
this(new ConcurrentHashMap<>());
}
/**
* Creates a new instance backed by the provided {@link Map}. This allows
* injecting a distributed {@link Map}.
*
* @param sessions the {@link Map} to use. Cannot be null.
*/
public MapReactorSessionRepository(Map<String, Session> sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
this.sessions = sessions;
}
/**
* Creates a new instance backed by the provided {@link Map}. This allows
* injecting a distributed {@link Map}.
*
* @param sessions the {@link Map} to use. Cannot be null.
*/
public MapReactorSessionRepository(Session... sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
this.sessions = new ConcurrentHashMap<>();
for(Session session : sessions) {
this.performSave(session);
}
}
/**
* Creates a new instance backed by the provided {@link Map}. This allows
* injecting a distributed {@link Map}.
*
* @param sessions the {@link Map} to use. Cannot be null.
*/
public MapReactorSessionRepository(Iterable<Session> sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
this.sessions = new ConcurrentHashMap<>();
for(Session session : sessions) {
this.performSave(session);
}
}
/**
* If non-null, this value is used to override
* {@link Session#setMaxInactiveInterval(Duration)}.
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
* should be kept alive between client requests.
*/
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = Integer.valueOf(defaultMaxInactiveInterval);
}
public Mono<Void> save(Session session) {
return Mono.fromRunnable(() -> performSave(session));
}
private void performSave(Session session) {
this.sessions.put(session.getId(), new MapSession(session));
}
public Mono<Session> findById(String id) {
return Mono.defer(() -> {
Session saved = this.sessions.get(id);
if (saved == null) {
return Mono.empty();
}
if (saved.isExpired()) {
delete(saved.getId());
return Mono.empty();
}
return Mono.just(new MapSession(saved));
});
}
public Mono<Void> delete(String id) {
return Mono.fromRunnable(() -> this.sessions.remove(id));
}
public Mono<Session> createSession() {
return Mono.defer(() -> {
Session result = new MapSession();
if (this.defaultMaxInactiveInterval != null) {
result.setMaxInactiveInterval(
Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return Mono.just(result);
});
}
}

View File

@@ -0,0 +1,148 @@
/*
* 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;
import org.junit.Before;
import org.junit.Test;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 2.0
*/
public class MapReactorSessionRepositoryTests {
MapReactorSessionRepository repository;
MapSession session;
@Before
public void setup() {
this.repository = new MapReactorSessionRepository();
this.session = new MapSession("session-id");
}
@Test
public void constructorVarargsThenFound() {
this.repository = new MapReactorSessionRepository(this.session);
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test(expected = IllegalArgumentException.class)
public void constructorVarargsWhenNullThenThrowsIllegalArgumentException() {
Session[] sessions = null;
new MapReactorSessionRepository(sessions);
}
@Test
public void constructorIterableThenFound() {
this.repository = new MapReactorSessionRepository(Arrays.asList(this.session));
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test(expected = IllegalArgumentException.class)
public void constructorIterableWhenNullThenThrowsIllegalArgumentException() {
Iterable<Session> sessions = null;
new MapReactorSessionRepository(sessions);
}
@Test
public void constructorMapThenFound() {
Map<String, Session> sessions = new HashMap<>();
sessions.put(this.session.getId(), this.session);
this.repository = new MapReactorSessionRepository(sessions);
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test(expected = IllegalArgumentException.class)
public void constructorMapWhenNullThenThrowsIllegalArgumentException() {
Map<String,Session> sessions = null;
new MapReactorSessionRepository(sessions);
}
@Test
public void saveWhenNoSubscribersThenNotFound() {
this.repository.save(this.session);
assertThat(this.repository.findById(this.session.getId()).block()).isNull();
}
@Test
public void saveWhenSubscriberThenFound() {
this.repository.save(this.session).block();
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test
public void findByIdWhenNotExpiredThenFound() {
this.repository = new MapReactorSessionRepository(this.session);
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test
public void findByIdWhenExpiredThenEmpty() {
this.session.setMaxInactiveInterval(Duration.ofSeconds(1));
this.session.setLastAccessedTime(Instant.now().minus(5, ChronoUnit.MINUTES));
this.repository = new MapReactorSessionRepository(Arrays.asList(this.session));
assertThat(this.repository.findById(this.session.getId()).block()).isNull();
}
@Test
public void createSessionWhenDefaultMaxInactiveIntervalThenDefaultMaxInactiveInterval() {
Session session = this.repository.createSession().block();
assertThat(session).isInstanceOf(MapSession.class);
assertThat(session.getMaxInactiveInterval())
.isEqualTo(new MapSession().getMaxInactiveInterval());
}
@Test
public void createSessionWhenCustomMaxInactiveIntervalThenCustomMaxInactiveInterval() {
final Duration expectedMaxInterval = new MapSession().getMaxInactiveInterval()
.plusSeconds(10);
this.repository.setDefaultMaxInactiveInterval(
(int) expectedMaxInterval.getSeconds());
Session session = this.repository.createSession().block();
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expectedMaxInterval);
}
}