Skip to content

Commit 6442954

Browse files
committed
Merge pull request #32046 from vpavic
* pr/32046: Use ReactiveFindByIndexNameSessionRepository Polish "Provide an Actuator endpoint for non-indexed session repositories" Provide an Actuator endpoint for non-indexed session repositories Closes gh-32046
2 parents a09cc22 + 6e3d4ed commit 6442954

File tree

10 files changed

+634
-122
lines changed

10 files changed

+634
-122
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,17 +16,25 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.session;
1818

19+
import org.springframework.beans.factory.ObjectProvider;
1920
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
21+
import org.springframework.boot.actuate.session.ReactiveSessionsEndpoint;
2022
import org.springframework.boot.actuate.session.SessionsEndpoint;
2123
import org.springframework.boot.autoconfigure.AutoConfiguration;
2224
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2325
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2426
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2527
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
2630
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration;
2731
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
2833
import org.springframework.session.FindByIndexNameSessionRepository;
34+
import org.springframework.session.ReactiveFindByIndexNameSessionRepository;
35+
import org.springframework.session.ReactiveSessionRepository;
2936
import org.springframework.session.Session;
37+
import org.springframework.session.SessionRepository;
3038

3139
/**
3240
* {@link EnableAutoConfiguration Auto-configuration} for {@link SessionsEndpoint}.
@@ -35,15 +43,36 @@
3543
* @since 2.0.0
3644
*/
3745
@AutoConfiguration(after = SessionAutoConfiguration.class)
38-
@ConditionalOnClass(FindByIndexNameSessionRepository.class)
46+
@ConditionalOnClass(Session.class)
3947
@ConditionalOnAvailableEndpoint(endpoint = SessionsEndpoint.class)
4048
public class SessionsEndpointAutoConfiguration {
4149

42-
@Bean
43-
@ConditionalOnBean(FindByIndexNameSessionRepository.class)
44-
@ConditionalOnMissingBean
45-
public SessionsEndpoint sessionEndpoint(FindByIndexNameSessionRepository<? extends Session> sessionRepository) {
46-
return new SessionsEndpoint(sessionRepository);
50+
@Configuration(proxyBeanMethods = false)
51+
@ConditionalOnWebApplication(type = Type.SERVLET)
52+
@ConditionalOnBean(SessionRepository.class)
53+
static class ServletSessionEndpointConfiguration {
54+
55+
@Bean
56+
@ConditionalOnMissingBean
57+
SessionsEndpoint sessionEndpoint(SessionRepository<? extends Session> sessionRepository,
58+
ObjectProvider<FindByIndexNameSessionRepository<? extends Session>> indexedSessionRepository) {
59+
return new SessionsEndpoint(sessionRepository, indexedSessionRepository.getIfAvailable());
60+
}
61+
62+
}
63+
64+
@Configuration(proxyBeanMethods = false)
65+
@ConditionalOnWebApplication(type = Type.REACTIVE)
66+
@ConditionalOnBean(ReactiveSessionRepository.class)
67+
static class ReactiveSessionEndpointConfiguration {
68+
69+
@Bean
70+
@ConditionalOnMissingBean
71+
ReactiveSessionsEndpoint sessionsEndpoint(ReactiveSessionRepository<? extends Session> sessionRepository,
72+
ObjectProvider<ReactiveFindByIndexNameSessionRepository<? extends Session>> indexedSessionRepository) {
73+
return new ReactiveSessionsEndpoint(sessionRepository, indexedSessionRepository.getIfAvailable());
74+
}
75+
4776
}
4877

4978
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ static class TestConfiguration {
125125

126126
@Bean
127127
SessionsEndpoint endpoint(FindByIndexNameSessionRepository<?> sessionRepository) {
128-
return new SessionsEndpoint(sessionRepository);
128+
return new SessionsEndpoint(sessionRepository, sessionRepository);
129129
}
130130

131131
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,14 +16,20 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.session;
1818

19+
import org.junit.jupiter.api.Nested;
1920
import org.junit.jupiter.api.Test;
2021

22+
import org.springframework.boot.actuate.session.ReactiveSessionsEndpoint;
2123
import org.springframework.boot.actuate.session.SessionsEndpoint;
2224
import org.springframework.boot.autoconfigure.AutoConfigurations;
23-
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
25+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
26+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
2427
import org.springframework.context.annotation.Bean;
2528
import org.springframework.context.annotation.Configuration;
2629
import org.springframework.session.FindByIndexNameSessionRepository;
30+
import org.springframework.session.ReactiveFindByIndexNameSessionRepository;
31+
import org.springframework.session.ReactiveSessionRepository;
32+
import org.springframework.session.SessionRepository;
2733

2834
import static org.assertj.core.api.Assertions.assertThat;
2935
import static org.mockito.Mockito.mock;
@@ -32,36 +38,117 @@
3238
* Tests for {@link SessionsEndpointAutoConfiguration}.
3339
*
3440
* @author Vedran Pavic
41+
* @author Moritz Halbritter
3542
*/
3643
class SessionsEndpointAutoConfigurationTests {
3744

38-
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
39-
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
40-
.withUserConfiguration(SessionConfiguration.class);
45+
@Nested
46+
class ServletSessionEndpointConfigurationTests {
4147

42-
@Test
43-
void runShouldHaveEndpointBean() {
44-
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sessions")
45-
.run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class));
46-
}
48+
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
49+
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
50+
.withUserConfiguration(IndexedSessionRepositoryConfiguration.class);
4751

48-
@Test
49-
void runWhenNotExposedShouldNotHaveEndpointBean() {
50-
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class));
51-
}
52+
@Test
53+
void runShouldHaveEndpointBean() {
54+
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sessions")
55+
.run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class));
56+
}
57+
58+
@Test
59+
void runWhenNoIndexedSessionRepositoryShouldHaveEndpointBean() {
60+
new WebApplicationContextRunner()
61+
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
62+
.withUserConfiguration(SessionRepositoryConfiguration.class)
63+
.withPropertyValues("management.endpoints.web.exposure.include=sessions")
64+
.run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class));
65+
}
66+
67+
@Test
68+
void runWhenNotExposedShouldNotHaveEndpointBean() {
69+
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class));
70+
}
71+
72+
@Test
73+
void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
74+
this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false")
75+
.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class));
76+
}
77+
78+
@Configuration(proxyBeanMethods = false)
79+
static class IndexedSessionRepositoryConfiguration {
80+
81+
@Bean
82+
FindByIndexNameSessionRepository<?> sessionRepository() {
83+
return mock(FindByIndexNameSessionRepository.class);
84+
}
85+
86+
}
87+
88+
@Configuration(proxyBeanMethods = false)
89+
static class SessionRepositoryConfiguration {
90+
91+
@Bean
92+
SessionRepository<?> sessionRepository() {
93+
return mock(SessionRepository.class);
94+
}
95+
96+
}
5297

53-
@Test
54-
void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
55-
this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false")
56-
.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class));
5798
}
5899

59-
@Configuration(proxyBeanMethods = false)
60-
static class SessionConfiguration {
100+
@Nested
101+
class ReactiveSessionEndpointConfigurationTests {
102+
103+
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
104+
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
105+
.withUserConfiguration(ReactiveSessionRepositoryConfiguration.class,
106+
ReactiveIndexedSessionRepositoryConfiguration.class);
107+
108+
@Test
109+
void runShouldHaveEndpointBean() {
110+
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sessions")
111+
.run((context) -> assertThat(context).hasSingleBean(ReactiveSessionsEndpoint.class));
112+
}
113+
114+
@Test
115+
void runWhenNoIndexedSessionRepositoryShouldHaveEndpointBean() {
116+
new ReactiveWebApplicationContextRunner()
117+
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
118+
.withUserConfiguration(ReactiveSessionRepositoryConfiguration.class)
119+
.withPropertyValues("management.endpoints.web.exposure.include=sessions")
120+
.run((context) -> assertThat(context).hasSingleBean(ReactiveSessionsEndpoint.class));
121+
}
122+
123+
@Test
124+
void runWhenNotExposedShouldNotHaveEndpointBean() {
125+
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveSessionsEndpoint.class));
126+
}
127+
128+
@Test
129+
void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
130+
this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false")
131+
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveSessionsEndpoint.class));
132+
}
133+
134+
@Configuration(proxyBeanMethods = false)
135+
static class ReactiveIndexedSessionRepositoryConfiguration {
136+
137+
@Bean
138+
ReactiveFindByIndexNameSessionRepository<?> indexedSessionRepository() {
139+
return mock(ReactiveFindByIndexNameSessionRepository.class);
140+
}
141+
142+
}
143+
144+
@Configuration(proxyBeanMethods = false)
145+
static class ReactiveSessionRepositoryConfiguration {
146+
147+
@Bean
148+
ReactiveSessionRepository<?> sessionRepository() {
149+
return mock(ReactiveSessionRepository.class);
150+
}
61151

62-
@Bean
63-
FindByIndexNameSessionRepository<?> sessionRepository() {
64-
return mock(FindByIndexNameSessionRepository.class);
65152
}
66153

67154
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2012-2024 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+
* https://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.boot.actuate.session;
18+
19+
import reactor.core.publisher.Mono;
20+
21+
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
22+
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
23+
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
24+
import org.springframework.boot.actuate.endpoint.annotation.Selector;
25+
import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor;
26+
import org.springframework.session.ReactiveFindByIndexNameSessionRepository;
27+
import org.springframework.session.ReactiveSessionRepository;
28+
import org.springframework.session.Session;
29+
import org.springframework.util.Assert;
30+
31+
/**
32+
* {@link Endpoint @Endpoint} to expose information about HTTP {@link Session}s on a
33+
* reactive stack.
34+
*
35+
* @author Vedran Pavic
36+
* @author Moritz Halbritter
37+
* @since 3.3.0
38+
*/
39+
@Endpoint(id = "sessions")
40+
public class ReactiveSessionsEndpoint {
41+
42+
private final ReactiveSessionRepository<? extends Session> sessionRepository;
43+
44+
private final ReactiveFindByIndexNameSessionRepository<? extends Session> indexedSessionRepository;
45+
46+
/**
47+
* Create a new {@link ReactiveSessionsEndpoint} instance.
48+
* @param sessionRepository the session repository
49+
* @param indexedSessionRepository the indexed session repository
50+
*/
51+
public ReactiveSessionsEndpoint(ReactiveSessionRepository<? extends Session> sessionRepository,
52+
ReactiveFindByIndexNameSessionRepository<? extends Session> indexedSessionRepository) {
53+
Assert.notNull(sessionRepository, "ReactiveSessionRepository must not be null");
54+
this.sessionRepository = sessionRepository;
55+
this.indexedSessionRepository = indexedSessionRepository;
56+
}
57+
58+
@ReadOperation
59+
public Mono<SessionsDescriptor> sessionsForUsername(String username) {
60+
if (this.indexedSessionRepository == null) {
61+
return Mono.empty();
62+
}
63+
return this.indexedSessionRepository.findByPrincipalName(username).map(SessionsDescriptor::new);
64+
}
65+
66+
@ReadOperation
67+
public Mono<SessionDescriptor> getSession(@Selector String sessionId) {
68+
return this.sessionRepository.findById(sessionId).map(SessionDescriptor::new);
69+
}
70+
71+
@DeleteOperation
72+
public Mono<Void> deleteSession(@Selector String sessionId) {
73+
return this.sessionRepository.deleteById(sessionId);
74+
}
75+
76+
}

0 commit comments

Comments
 (0)