Skip to content

Commit bbf3dc7

Browse files
committed
Add SafeRetrievingSessionRepository
1 parent 0907423 commit bbf3dc7

File tree

2 files changed

+375
-0
lines changed

2 files changed

+375
-0
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright 2014-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session;
18+
19+
import java.util.Collections;
20+
import java.util.Map;
21+
import java.util.Set;
22+
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* A {@link FindByIndexNameSessionRepository} that decorates the delegating
27+
* {@link SessionRepository}, ignoring the configured set of exceptions during retrieval
28+
* of a {@link Session} from the underlying store. Useful with handling exceptions
29+
* during session deserialization (for example, when serialization UID changes) in
30+
* scenarios where {@link SessionRepository} consumer wants to treat the session that
31+
* wasn't deserializable as non-existing.
32+
* <p>
33+
* By default, this implementation will delete the session whose retrieval using
34+
* {@link #getSession(String)} has failed with the configured ignored exception. This
35+
* behavior can be changed using {@link #setDeleteOnIgnoredException(boolean)} method.
36+
*
37+
* @param <S> the type of {@link Session} being managed
38+
* @author Vedran Pavic
39+
* @since 1.3.0
40+
*/
41+
public class SafeRetrievingSessionRepository<S extends Session>
42+
implements FindByIndexNameSessionRepository<S> {
43+
44+
private final FindByIndexNameSessionRepository<S> delegate;
45+
46+
private final Set<Class<? extends RuntimeException>> ignoredExceptions;
47+
48+
private boolean deleteOnIgnoredException = true;
49+
50+
/**
51+
* Create a new {@link SafeRetrievingSessionRepository} instance backed by a
52+
* {@link FindByIndexNameSessionRepository} delegate.
53+
* @param delegate the {@link FindByIndexNameSessionRepository} delegate
54+
* @param ignoredExceptions the set of exceptions to ignore
55+
*/
56+
public SafeRetrievingSessionRepository(
57+
FindByIndexNameSessionRepository<S> delegate,
58+
Set<Class<? extends RuntimeException>> ignoredExceptions) {
59+
Assert.notNull(delegate, "Delegate must not be null");
60+
Assert.notEmpty(ignoredExceptions, "Ignored exceptions must not be empty");
61+
this.delegate = delegate;
62+
this.ignoredExceptions = ignoredExceptions;
63+
}
64+
65+
/**
66+
* Create a new {@link SafeRetrievingSessionRepository} instance backed by a
67+
* {@link SessionRepository} delegate.
68+
* @param delegate the {@link SessionRepository} delegate
69+
* @param ignoredExceptions the set of exceptions to ignore
70+
*/
71+
public SafeRetrievingSessionRepository(SessionRepository<S> delegate,
72+
Set<Class<? extends RuntimeException>> ignoredExceptions) {
73+
this(new FindByIndexNameSessionRepositoryAdapter<S>(delegate), ignoredExceptions);
74+
}
75+
76+
/**
77+
* Set whether session should be deleted after ignored exception has occurred during
78+
* retrieval.
79+
* @param deleteOnIgnoredException the flag to indicate whether session should be
80+
* deleted
81+
*/
82+
public void setDeleteOnIgnoredException(boolean deleteOnIgnoredException) {
83+
this.deleteOnIgnoredException = deleteOnIgnoredException;
84+
}
85+
86+
public S createSession() {
87+
return this.delegate.createSession();
88+
}
89+
90+
public void save(S session) {
91+
this.delegate.save(session);
92+
}
93+
94+
public S getSession(String id) {
95+
try {
96+
return this.delegate.getSession(id);
97+
}
98+
catch (RuntimeException e) {
99+
if (isIgnoredException(e)) {
100+
if (this.deleteOnIgnoredException) {
101+
this.delegate.delete(id);
102+
}
103+
return null;
104+
}
105+
throw e;
106+
}
107+
}
108+
109+
public void delete(String id) {
110+
this.delegate.delete(id);
111+
}
112+
113+
public Map<String, S> findByIndexNameAndIndexValue(String indexName, String indexValue) {
114+
try {
115+
return this.delegate.findByIndexNameAndIndexValue(indexName, indexValue);
116+
}
117+
catch (RuntimeException e) {
118+
if (isIgnoredException(e)) {
119+
return Collections.emptyMap();
120+
}
121+
throw e;
122+
}
123+
}
124+
125+
public boolean isIgnoredException(RuntimeException e) {
126+
return this.ignoredExceptions.contains(e.getClass());
127+
}
128+
129+
private static class FindByIndexNameSessionRepositoryAdapter<S extends Session>
130+
implements FindByIndexNameSessionRepository<S> {
131+
132+
private final SessionRepository<S> delegate;
133+
134+
FindByIndexNameSessionRepositoryAdapter(SessionRepository<S> delegate) {
135+
Assert.notNull(delegate, "Delegate must not be null");
136+
this.delegate = delegate;
137+
}
138+
139+
public S createSession() {
140+
return this.delegate.createSession();
141+
}
142+
143+
public void save(S session) {
144+
this.delegate.save(session);
145+
}
146+
147+
public S getSession(String id) {
148+
return this.delegate.getSession(id);
149+
}
150+
151+
public void delete(String id) {
152+
this.delegate.delete(id);
153+
}
154+
155+
public Map<String, S> findByIndexNameAndIndexValue(String indexName,
156+
String indexValue) {
157+
throw new UnsupportedOperationException();
158+
}
159+
160+
}
161+
162+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* Copyright 2014-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session;
18+
19+
import java.util.Collections;
20+
import java.util.UUID;
21+
22+
import org.junit.Rule;
23+
import org.junit.Test;
24+
import org.junit.rules.ExpectedException;
25+
import org.junit.runner.RunWith;
26+
import org.mockito.Mock;
27+
import org.mockito.runners.MockitoJUnitRunner;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.mockito.BDDMockito.given;
31+
import static org.mockito.Matchers.any;
32+
import static org.mockito.Matchers.anyString;
33+
import static org.mockito.Mockito.mock;
34+
import static org.mockito.Mockito.times;
35+
import static org.mockito.Mockito.verify;
36+
import static org.mockito.Mockito.verifyZeroInteractions;
37+
38+
/**
39+
* Tests for {@link SafeRetrievingSessionRepository}.
40+
*
41+
* @author Vedran Pavic
42+
*/
43+
@RunWith(MockitoJUnitRunner.class)
44+
public class SafeRetrievingSessionRepositoryTests {
45+
46+
@Rule
47+
public ExpectedException thrown = ExpectedException.none();
48+
49+
@Mock
50+
private FindByIndexNameSessionRepository<ExpiringSession> delegate;
51+
52+
@Test
53+
public void createWithNullDelegateFails() {
54+
this.thrown.expect(IllegalArgumentException.class);
55+
this.thrown.expectMessage("Delegate must not be null");
56+
new SafeRetrievingSessionRepository<Session>(null,
57+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
58+
}
59+
60+
@Test
61+
public void createWithNullIgnoredExceptionsFails() {
62+
this.thrown.expect(IllegalArgumentException.class);
63+
this.thrown.expectMessage("Ignored exceptions must not be empty");
64+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate, null);
65+
}
66+
67+
@Test
68+
public void createWithEmptyIgnoredExceptionsFails() {
69+
this.thrown.expect(IllegalArgumentException.class);
70+
this.thrown.expectMessage("Ignored exceptions must not be empty");
71+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
72+
Collections.<Class<? extends RuntimeException>>emptySet());
73+
}
74+
75+
@Test
76+
public void createSession() {
77+
SafeRetrievingSessionRepository<ExpiringSession> repository =
78+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
79+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
80+
repository.createSession();
81+
verify(this.delegate, times(1)).createSession();
82+
verifyZeroInteractions(this.delegate);
83+
}
84+
85+
@Test
86+
public void saveSession() {
87+
SafeRetrievingSessionRepository<ExpiringSession> repository =
88+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
89+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
90+
repository.save(new MapSession());
91+
verify(this.delegate, times(1)).save(any(ExpiringSession.class));
92+
verifyZeroInteractions(this.delegate);
93+
}
94+
95+
@Test
96+
public void getSession() {
97+
ExpiringSession session = mock(ExpiringSession.class);
98+
given(this.delegate.getSession(anyString())).willReturn(session);
99+
SafeRetrievingSessionRepository<ExpiringSession> repository =
100+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
101+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
102+
assertThat(repository.getSession(UUID.randomUUID().toString()))
103+
.isEqualTo(session);
104+
verify(this.delegate, times(1)).getSession(anyString());
105+
verifyZeroInteractions(this.delegate);
106+
}
107+
108+
@Test
109+
@SuppressWarnings("unchecked")
110+
public void getSessionThrowsIgnoredException() {
111+
given(this.delegate.getSession(anyString())).willThrow(RuntimeException.class);
112+
SafeRetrievingSessionRepository<ExpiringSession> repository =
113+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
114+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
115+
assertThat(repository.getSession(UUID.randomUUID().toString())).isNull();
116+
verify(this.delegate, times(1)).getSession(anyString());
117+
verify(this.delegate, times(1)).delete(anyString());
118+
verifyZeroInteractions(this.delegate);
119+
}
120+
121+
@Test
122+
@SuppressWarnings("unchecked")
123+
public void getSessionThrowsIgnoredExceptionWithDeletionDisabled() {
124+
given(this.delegate.getSession(anyString())).willThrow(RuntimeException.class);
125+
SafeRetrievingSessionRepository<ExpiringSession> repository =
126+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
127+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
128+
repository.setDeleteOnIgnoredException(false);
129+
assertThat(repository.getSession(UUID.randomUUID().toString())).isNull();
130+
verify(this.delegate, times(1)).getSession(anyString());
131+
verifyZeroInteractions(this.delegate);
132+
}
133+
134+
@Test
135+
@SuppressWarnings("unchecked")
136+
public void getSessionThrowsNotIgnoredException() {
137+
this.thrown.expect(RuntimeException.class);
138+
given(this.delegate.getSession(anyString())).willThrow(RuntimeException.class);
139+
SafeRetrievingSessionRepository<ExpiringSession> repository =
140+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
141+
Collections.<Class<? extends RuntimeException>>singleton(IllegalStateException.class));
142+
repository.getSession(UUID.randomUUID().toString());
143+
verify(this.delegate, times(1)).getSession(anyString());
144+
verifyZeroInteractions(this.delegate);
145+
}
146+
147+
@Test
148+
public void deleteSession() {
149+
SafeRetrievingSessionRepository<ExpiringSession> repository =
150+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
151+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
152+
repository.delete(UUID.randomUUID().toString());
153+
verify(this.delegate, times(1)).delete(anyString());
154+
verifyZeroInteractions(this.delegate);
155+
}
156+
157+
@Test
158+
public void findByIndexNameAndIndexValue() {
159+
ExpiringSession session = mock(ExpiringSession.class);
160+
given(this.delegate.findByIndexNameAndIndexValue(anyString(), anyString()))
161+
.willReturn(Collections.singletonMap("name", session));
162+
SafeRetrievingSessionRepository<ExpiringSession> repository =
163+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
164+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
165+
assertThat(repository.findByIndexNameAndIndexValue("name", "value").get("name"))
166+
.isEqualTo(session);
167+
verify(this.delegate, times(1)).findByIndexNameAndIndexValue(anyString(),
168+
anyString());
169+
verifyZeroInteractions(this.delegate);
170+
}
171+
172+
@Test
173+
@SuppressWarnings("unchecked")
174+
public void findByIndexNameAndIndexValueThrowsIgnoredException() {
175+
given(this.delegate.findByIndexNameAndIndexValue(anyString(), anyString()))
176+
.willThrow(RuntimeException.class);
177+
SafeRetrievingSessionRepository<ExpiringSession> repository =
178+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
179+
Collections.<Class<? extends RuntimeException>>singleton(RuntimeException.class));
180+
assertThat(repository.findByIndexNameAndIndexValue("name", "value")).isEmpty();
181+
verify(this.delegate, times(1)).findByIndexNameAndIndexValue(anyString(),
182+
anyString());
183+
verifyZeroInteractions(this.delegate);
184+
}
185+
186+
@Test
187+
@SuppressWarnings("unchecked")
188+
public void findByIndexNameAndIndexValueThrowsNotIgnoredException() {
189+
this.thrown.expect(RuntimeException.class);
190+
given(this.delegate.findByIndexNameAndIndexValue(anyString(), anyString()))
191+
.willThrow(RuntimeException.class);
192+
SafeRetrievingSessionRepository<ExpiringSession> repository =
193+
new SafeRetrievingSessionRepository<ExpiringSession>(this.delegate,
194+
Collections.<Class<? extends RuntimeException>>singleton(IllegalStateException.class));
195+
repository.findByIndexNameAndIndexValue("name", "value");
196+
verify(this.delegate, times(1)).findByIndexNameAndIndexValue(anyString(),
197+
anyString());
198+
verifyZeroInteractions(this.delegate);
199+
}
200+
201+
@Test
202+
@SuppressWarnings("unchecked")
203+
public void findByIndexNameAndIndexValueOnSessionRepositoryThrowsException() {
204+
this.thrown.expect(UnsupportedOperationException.class);
205+
SessionRepository delegate = mock(SessionRepository.class);
206+
SafeRetrievingSessionRepository<ExpiringSession> repository =
207+
new SafeRetrievingSessionRepository<ExpiringSession>(delegate,
208+
Collections.<Class<? extends RuntimeException>>singleton(IllegalStateException.class));
209+
repository.findByIndexNameAndIndexValue("name", "value");
210+
verifyZeroInteractions(delegate);
211+
}
212+
213+
}

0 commit comments

Comments
 (0)