Skip to content

Commit 6351eea

Browse files
committed
Add session endpoints
1 parent ebb0ff8 commit 6351eea

File tree

10 files changed

+455
-1
lines changed

10 files changed

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

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.Map;
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 Map<Object, 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)