Skip to content

Commit 94c9eda

Browse files
committed
Add session endpoints
1 parent b8df4d4 commit 94c9eda

File tree

5 files changed

+317
-1
lines changed

5 files changed

+317
-1
lines changed

spring-boot-actuator/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@
213213
<artifactId>spring-security-config</artifactId>
214214
<optional>true</optional>
215215
</dependency>
216+
<dependency>
217+
<groupId>org.springframework.session</groupId>
218+
<artifactId>spring-session</artifactId>
219+
<optional>true</optional>
220+
</dependency>
216221
<dependency>
217222
<groupId>org.apache.tomcat.embed</groupId>
218223
<artifactId>tomcat-embed-core</artifactId>

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,22 @@
3535
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
3636
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
3737
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
38+
import org.springframework.boot.actuate.endpoint.mvc.SessionMvcEndpoint;
3839
import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint;
3940
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
4041
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
42+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
4143
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4244
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
4345
import org.springframework.boot.bind.RelaxedPropertyResolver;
4446
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4547
import org.springframework.context.annotation.Bean;
4648
import org.springframework.context.annotation.ConditionContext;
4749
import org.springframework.context.annotation.Conditional;
50+
import org.springframework.context.annotation.Configuration;
4851
import org.springframework.core.env.Environment;
4952
import org.springframework.core.type.AnnotatedTypeMetadata;
53+
import org.springframework.session.FindByIndexNameSessionRepository;
5054
import org.springframework.util.CollectionUtils;
5155
import org.springframework.util.StringUtils;
5256
import org.springframework.web.cors.CorsConfiguration;
@@ -55,6 +59,7 @@
5559
* Configuration to expose {@link Endpoint} instances over Spring MVC.
5660
*
5761
* @author Dave Syer
62+
* @author Eddú Meléndez
5863
* @since 1.3.0
5964
*/
6065
@ManagementContextConfiguration
@@ -167,6 +172,19 @@ public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) {
167172
return new ShutdownMvcEndpoint(delegate);
168173
}
169174

175+
@Configuration
176+
@ConditionalOnClass(FindByIndexNameSessionRepository.class)
177+
@ConditionalOnBean(FindByIndexNameSessionRepository.class)
178+
static class SessionMvcEndpointConfiguration {
179+
180+
@Bean
181+
@ConditionalOnEnabledEndpoint(value = "session", enabledByDefault = false)
182+
public SessionMvcEndpoint sessionMvcEndpoint() {
183+
return new SessionMvcEndpoint();
184+
}
185+
186+
}
187+
170188
private static class LogFileCondition extends SpringBootCondition {
171189

172190
@Override
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2012-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.boot.actuate.endpoint.mvc;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.actuate.endpoint.Endpoint;
24+
import org.springframework.boot.context.properties.ConfigurationProperties;
25+
import org.springframework.session.ExpiringSession;
26+
import org.springframework.session.FindByIndexNameSessionRepository;
27+
import org.springframework.web.bind.annotation.PathVariable;
28+
import org.springframework.web.bind.annotation.RequestMapping;
29+
import org.springframework.web.bind.annotation.RequestMethod;
30+
import org.springframework.web.bind.annotation.RequestParam;
31+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
32+
33+
/**
34+
* {@link MvcEndpoint} to expose actuator session.
35+
*
36+
* @author Eddú Meléndez
37+
* @since 1.4.0
38+
*/
39+
@ConfigurationProperties("endpoints.session")
40+
public class SessionMvcEndpoint extends WebMvcConfigurerAdapter
41+
implements MvcEndpoint {
42+
43+
/**
44+
* Endpoint URL path.
45+
*/
46+
private String path = "/session";
47+
48+
/**
49+
* Enable the endpoint.
50+
*/
51+
private boolean enabled = true;
52+
53+
@Autowired
54+
private FindByIndexNameSessionRepository<? extends ExpiringSession> sessionRepository;
55+
56+
@RequestMapping(method = RequestMethod.GET)
57+
public List<Session> result(@RequestParam String username) {
58+
List<Session> sessions = new ArrayList<Session>();
59+
for (ExpiringSession session : this.sessionRepository.findByIndexNameAndIndexValue(
60+
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username)
61+
.values()) {
62+
sessions.add(new Session(session));
63+
}
64+
return sessions;
65+
}
66+
67+
@RequestMapping(path = "/{sessionId}", method = RequestMethod.DELETE)
68+
public void delete(@PathVariable String sessionId) {
69+
ExpiringSession session = this.sessionRepository.getSession(sessionId);
70+
if (session != null) {
71+
this.sessionRepository.delete(sessionId);
72+
}
73+
}
74+
75+
public void setPath(String path) {
76+
this.path = path;
77+
}
78+
79+
@Override
80+
public String getPath() {
81+
return this.path;
82+
}
83+
84+
public void setEnabled(boolean enabled) {
85+
this.enabled = enabled;
86+
}
87+
88+
public boolean isEnabled() {
89+
return this.enabled;
90+
}
91+
92+
@Override
93+
public boolean isSensitive() {
94+
return true;
95+
}
96+
97+
@Override
98+
public Class<? extends Endpoint> getEndpointType() {
99+
return Endpoint.class;
100+
}
101+
102+
/**
103+
* Session properties.
104+
*/
105+
public static class Session {
106+
107+
private String id;
108+
109+
private long creationTime;
110+
111+
private long lastAccessedTime;
112+
113+
private long maxInactiveIntervalInSeconds;
114+
115+
public Session(ExpiringSession session) {
116+
this.id = session.getId();
117+
this.creationTime = session.getCreationTime();
118+
this.lastAccessedTime = session.getLastAccessedTime();
119+
this.maxInactiveIntervalInSeconds = session.getMaxInactiveIntervalInSeconds();
120+
}
121+
122+
public String getId() {
123+
return this.id;
124+
}
125+
126+
public void setId(String id) {
127+
this.id = id;
128+
}
129+
130+
public long getCreationTime() {
131+
return this.creationTime;
132+
}
133+
134+
public void setCreationTime(long creationTime) {
135+
this.creationTime = creationTime;
136+
}
137+
138+
public long getLastAccessedTime() {
139+
return this.lastAccessedTime;
140+
}
141+
142+
public void setLastAccessedTime(long lastAccessedTime) {
143+
this.lastAccessedTime = lastAccessedTime;
144+
}
145+
146+
public long getMaxInactiveIntervalInSeconds() {
147+
return this.maxInactiveIntervalInSeconds;
148+
}
149+
150+
public void setMaxInactiveIntervalInSeconds(long maxInactiveIntervalInSeconds) {
151+
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
152+
}
153+
154+
}
155+
156+
}

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
2828
import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration;
2929
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration;
3031
import org.springframework.boot.builder.SpringApplicationBuilder;
3132
import org.springframework.boot.test.util.ApplicationContextTestUtils;
3233
import org.springframework.context.ConfigurableApplicationContext;
@@ -65,7 +66,8 @@ public void testChild() {
6566
ElasticsearchRepositoriesAutoConfiguration.class,
6667
CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class,
6768
Neo4jAutoConfiguration.class, RedisAutoConfiguration.class,
68-
RedisRepositoriesAutoConfiguration.class }, excludeName = {
69+
RedisRepositoriesAutoConfiguration.class,
70+
SessionAutoConfiguration.class }, excludeName = {
6971
"org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration" })
7072
public static class Child {
7173

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2012-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.boot.actuate.endpoint.mvc;
18+
19+
import org.junit.After;
20+
import org.junit.Before;
21+
import org.junit.Test;
22+
23+
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
24+
import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
25+
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
26+
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
27+
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
28+
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor;
29+
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
30+
import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory;
31+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
32+
import org.springframework.boot.test.util.EnvironmentTestUtils;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.context.annotation.Import;
36+
import org.springframework.session.FindByIndexNameSessionRepository;
37+
import org.springframework.test.web.servlet.MockMvc;
38+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
39+
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
40+
41+
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.mockito.Mockito.mock;
43+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
44+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
45+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
46+
47+
/**
48+
* Tests for {@link SessionMvcEndpoint}
49+
*
50+
* @author Eddú Meléndez
51+
*/
52+
public class SessionMvcEndpointTests {
53+
54+
private AnnotationConfigEmbeddedWebApplicationContext context;
55+
56+
@Before
57+
public void setUp() {
58+
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
59+
}
60+
61+
@After
62+
public void close() {
63+
if (this.context != null) {
64+
this.context.close();
65+
}
66+
}
67+
68+
@Test
69+
public void endpointNotRegistered() throws Exception {
70+
loadConfig();
71+
assertThat(this.context.getBeanNamesForType(SessionMvcEndpoint.class)).hasSize(0);
72+
}
73+
74+
@Test
75+
public void endpointRegistered() throws Exception {
76+
EnvironmentTestUtils.addEnvironment(this.context, "endpoints.session.enabled=true");
77+
loadConfig();
78+
assertThat(this.context.getBeanNamesForType(SessionMvcEndpoint.class)).hasSize(1);
79+
}
80+
81+
@Test
82+
public void searchEndpointAvailable() throws Exception {
83+
EnvironmentTestUtils.addEnvironment(this.context, "endpoints.session.enabled=true");
84+
loadConfig();
85+
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
86+
mockMvc.perform((get("/session/?username=springuser")))
87+
.andExpect(status().isOk());
88+
}
89+
90+
@Test
91+
public void deleteEndpointAvailable() throws Exception {
92+
EnvironmentTestUtils.addEnvironment(this.context, "endpoints.session.enabled=true");
93+
loadConfig();
94+
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
95+
mockMvc.perform(delete("/session/123"))
96+
.andExpect(status().isOk());
97+
}
98+
99+
private void loadConfig() {
100+
this.context.register(Config.class, SessionRepositoryConfig.class,
101+
WebMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class);
102+
this.context.refresh();
103+
}
104+
105+
@Configuration
106+
@EnableConfigurationProperties
107+
@EnableWebMvc
108+
@Import({EndpointWebMvcAutoConfiguration.class,
109+
ManagementServerPropertiesAutoConfiguration.class})
110+
public static class SessionRepositoryConfig {
111+
112+
@Bean
113+
public FindByIndexNameSessionRepository findByIndexNameSessionRepository() {
114+
return mock(FindByIndexNameSessionRepository.class);
115+
}
116+
117+
}
118+
119+
@Configuration
120+
@EnableConfigurationProperties
121+
protected static class Config {
122+
123+
@Bean
124+
public EmbeddedServletContainerFactory containerFactory() {
125+
return new MockEmbeddedServletContainerFactory();
126+
}
127+
128+
@Bean
129+
public EmbeddedServletContainerCustomizerBeanPostProcessor embeddedServletContainerCustomizerBeanPostProcessor() {
130+
return new EmbeddedServletContainerCustomizerBeanPostProcessor();
131+
}
132+
133+
}
134+
135+
}

0 commit comments

Comments
 (0)