Skip to content

Commit ba6d67d

Browse files
committed
Add session endpoints
1 parent b8df4d4 commit ba6d67d

File tree

5 files changed

+315
-1
lines changed

5 files changed

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

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)