Skip to content

Commit 18b3716

Browse files
committed
Add session endpoints
1 parent ebb0ff8 commit 18b3716

File tree

10 files changed

+450
-1
lines changed

10 files changed

+450
-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/EndpointAutoConfiguration.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
4141
import org.springframework.boot.actuate.endpoint.PublicMetrics;
4242
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
43+
import org.springframework.boot.actuate.endpoint.SessionEndpoint;
4344
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
4445
import org.springframework.boot.actuate.endpoint.TraceEndpoint;
4546
import org.springframework.boot.actuate.health.HealthAggregator;
@@ -61,6 +62,7 @@
6162
import org.springframework.context.annotation.Bean;
6263
import org.springframework.context.annotation.Configuration;
6364
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
65+
import org.springframework.session.FindByIndexNameSessionRepository;
6466
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
6567

6668
/**
@@ -216,4 +218,17 @@ public RequestMappingEndpoint requestMappingEndpoint() {
216218

217219
}
218220

221+
@Configuration
222+
@ConditionalOnBean(FindByIndexNameSessionRepository.class)
223+
@ConditionalOnClass(FindByIndexNameSessionRepository.class)
224+
static class SessionEndpointConfiguration {
225+
226+
@Bean
227+
@ConditionalOnMissingBean
228+
public SessionEndpoint sessionEndpoint() {
229+
return new SessionEndpoint();
230+
}
231+
232+
}
233+
219234
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
2727
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
2828
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
29+
import org.springframework.boot.actuate.endpoint.SessionEndpoint;
2930
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
3031
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
3132
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer;
@@ -35,6 +36,7 @@
3536
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
3637
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
3738
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
39+
import org.springframework.boot.actuate.endpoint.mvc.SessionMvcEndpoint;
3840
import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint;
3941
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
4042
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -55,6 +57,7 @@
5557
* Configuration to expose {@link Endpoint} instances over Spring MVC.
5658
*
5759
* @author Dave Syer
60+
* @author Eddú Meléndez
5861
* @since 1.3.0
5962
*/
6063
@ManagementContextConfiguration
@@ -167,6 +170,13 @@ public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) {
167170
return new ShutdownMvcEndpoint(delegate);
168171
}
169172

173+
@Bean
174+
@ConditionalOnBean(SessionEndpoint.class)
175+
@ConditionalOnEnabledEndpoint(value = "session", enabledByDefault = false)
176+
public SessionMvcEndpoint sessionMvcEndpoint(SessionEndpoint delegate) {
177+
return new SessionMvcEndpoint(delegate);
178+
}
179+
170180
private static class LogFileCondition extends SpringBootCondition {
171181

172182
@Override
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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;
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.context.properties.ConfigurationProperties;
24+
import org.springframework.session.ExpiringSession;
25+
import org.springframework.session.FindByIndexNameSessionRepository;
26+
27+
/**
28+
* {@link Endpoint} to manage web sessions.
29+
*
30+
* @author Eddú Meléndez
31+
* @since 1.4.0
32+
*/
33+
@ConfigurationProperties("endpoints.session")
34+
public class SessionEndpoint implements Endpoint<Object> {
35+
36+
@Autowired
37+
private FindByIndexNameSessionRepository<? extends ExpiringSession> sessionRepository;
38+
39+
/**
40+
* Enable the endpoint.
41+
*/
42+
private boolean enabled = false;
43+
44+
@Override
45+
public String getId() {
46+
return "session";
47+
}
48+
49+
@Override
50+
public boolean isEnabled() {
51+
return this.enabled;
52+
}
53+
54+
@Override
55+
public boolean isSensitive() {
56+
return true;
57+
}
58+
59+
@Override
60+
public Object invoke() {
61+
return null;
62+
}
63+
64+
public List<Object> result(String username) {
65+
List<Object> sessions = new ArrayList<Object>();
66+
for (ExpiringSession session : this.sessionRepository.findByIndexNameAndIndexValue(
67+
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username)
68+
.values()) {
69+
sessions.add(new Session(session));
70+
}
71+
return sessions;
72+
}
73+
74+
public boolean delete(String sessionId) {
75+
ExpiringSession session = this.sessionRepository.getSession(sessionId);
76+
if (session != null) {
77+
this.sessionRepository.delete(sessionId);
78+
return true;
79+
}
80+
return false;
81+
}
82+
83+
/**
84+
* Session properties.
85+
*/
86+
public static class Session {
87+
88+
private String id;
89+
90+
private long creationTime;
91+
92+
private long lastAccessedTime;
93+
94+
public Session(ExpiringSession session) {
95+
this.id = session.getId();
96+
this.creationTime = session.getCreationTime();
97+
this.lastAccessedTime = session.getLastAccessedTime();
98+
}
99+
100+
public String getId() {
101+
return this.id;
102+
}
103+
104+
public void setId(String id) {
105+
this.id = id;
106+
}
107+
108+
public long getCreationTime() {
109+
return this.creationTime;
110+
}
111+
112+
public void setCreationTime(long creationTime) {
113+
this.creationTime = creationTime;
114+
}
115+
116+
public long getLastAccessedTime() {
117+
return this.lastAccessedTime;
118+
}
119+
120+
public void setLastAccessedTime(long lastAccessedTime) {
121+
this.lastAccessedTime = lastAccessedTime;
122+
}
123+
124+
}
125+
126+
}

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
*
5959
* @author Christian Dupuis
6060
* @author Andy Wilkinson
61+
* @author Eddú Meléndez
6162
*/
6263
public class EndpointMBeanExporter extends MBeanExporter
6364
implements SmartLifecycle, ApplicationContextAware {
@@ -188,6 +189,9 @@ protected void registerEndpoint(String beanName, Endpoint<?> endpoint) {
188189
}
189190

190191
protected EndpointMBean getEndpointMBean(String beanName, Endpoint<?> endpoint) {
192+
if (endpoint instanceof SessionEndpointMBean) {
193+
return new SessionEndpointMBean(beanName, endpoint, this.objectMapper);
194+
}
191195
if (endpoint instanceof ShutdownEndpoint) {
192196
return new ShutdownEndpointMBean(beanName, endpoint, this.objectMapper);
193197
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.jmx;
18+
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
21+
import org.springframework.boot.actuate.endpoint.Endpoint;
22+
import org.springframework.boot.actuate.endpoint.SessionEndpoint;
23+
import org.springframework.jmx.export.annotation.ManagedOperation;
24+
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
25+
import org.springframework.jmx.export.annotation.ManagedResource;
26+
27+
/**
28+
* Special endpoint wrapper for {@link SessionEndpoint}.
29+
*
30+
* @author Eddú Meléndez
31+
* @since 1.4.0
32+
*/
33+
@ManagedResource
34+
public class SessionEndpointMBean extends EndpointMBean {
35+
36+
private SessionEndpoint sessionEndpoint;
37+
38+
/**
39+
* Create a new {@link SessionEndpointMBean} instance.
40+
*
41+
* @param beanName the bean name
42+
* @param endpoint the endpoint to wrap
43+
* @param objectMapper the {@link ObjectMapper} used to convert the payload
44+
*/
45+
public SessionEndpointMBean(String beanName, Endpoint<?> endpoint, ObjectMapper objectMapper) {
46+
super(beanName, endpoint, objectMapper);
47+
this.sessionEndpoint = (SessionEndpoint) getEndpoint();
48+
}
49+
50+
@ManagedOperation(description = "Find current user's sessions")
51+
@ManagedOperationParameter(name = "username", description = "Application's username")
52+
public Object findSessionsByUsername(String username) {
53+
return convert(this.sessionEndpoint.result(username));
54+
}
55+
56+
@ManagedOperation(description = "Delete session by id")
57+
@ManagedOperationParameter(name = "sessionId", description = "Web session id")
58+
public boolean deleteSessionBySessionId(String sessionId) {
59+
return this.sessionEndpoint.delete(sessionId);
60+
}
61+
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.List;
20+
21+
import org.springframework.boot.actuate.endpoint.Endpoint;
22+
import org.springframework.boot.actuate.endpoint.SessionEndpoint;
23+
import org.springframework.boot.context.properties.ConfigurationProperties;
24+
import org.springframework.http.ResponseEntity;
25+
import org.springframework.web.bind.annotation.PathVariable;
26+
import org.springframework.web.bind.annotation.RequestMapping;
27+
import org.springframework.web.bind.annotation.RequestMethod;
28+
import org.springframework.web.bind.annotation.RequestParam;
29+
30+
/**
31+
* Adapter to expose {@link SessionEndpoint} as an {@link MvcEndpoint}.
32+
*
33+
* @author Eddú Meléndez
34+
* @since 1.4.0
35+
*/
36+
@ConfigurationProperties(prefix = "endpoints.session")
37+
public class SessionMvcEndpoint extends AbstractEndpointMvcAdapter<SessionEndpoint> {
38+
39+
/**
40+
* Create a new {@link EndpointMvcAdapter}.
41+
*
42+
* @param delegate the underlying {@link Endpoint} to adapt.
43+
*/
44+
public SessionMvcEndpoint(SessionEndpoint delegate) {
45+
super(delegate);
46+
}
47+
48+
@RequestMapping(method = RequestMethod.GET)
49+
public List<Object> result(@RequestParam String username) {
50+
return getDelegate().result(username);
51+
}
52+
53+
@RequestMapping(path = "/{sessionId}", method = RequestMethod.DELETE)
54+
public ResponseEntity delete(@PathVariable String sessionId) {
55+
boolean deleted = getDelegate().delete(sessionId);
56+
if (deleted) {
57+
return ResponseEntity.ok().build();
58+
}
59+
return ResponseEntity.notFound().build();
60+
}
61+
62+
}

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

0 commit comments

Comments
 (0)