diff --git a/settings.gradle b/settings.gradle index 5c190e7bf..1fe5de151 100644 --- a/settings.gradle +++ b/settings.gradle @@ -28,3 +28,4 @@ include 'spring-session-data-gemfire' include 'spring-session-data-redis' include 'spring-session-jdbc' include 'spring-session-data-mongo' +include 'spring-session-boot' diff --git a/spring-session-boot/build.gradle b/spring-session-boot/build.gradle new file mode 100644 index 000000000..907adbffd --- /dev/null +++ b/spring-session-boot/build.gradle @@ -0,0 +1,32 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion") + } +} + +apply from: JAVA_GRADLE +apply plugin: 'spring-io' + +description = "Aggregator for Spring Session and Spring Boot" + +dependencies { + optional project(':spring-session'), + "org.springframework.boot:spring-boot-actuator:$springBootVersion", + "org.springframework.boot:spring-boot-starter-jdbc:$springBootVersion", + "org.springframework.boot:spring-boot-starter-web:$springBootVersion" + + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion", + "com.h2database:h2:$h2Version", + "org.assertj:assertj-core:$assertjVersion" +} + +dependencyManagement { + springIoTestRuntime { + imports { + mavenBom "io.spring.platform:platform-bom:${springIoVersion}" + } + } +} diff --git a/spring-session-boot/src/main/java/org/springframework/session/endpoint/EndpointConfiguration.java b/spring-session-boot/src/main/java/org/springframework/session/endpoint/EndpointConfiguration.java new file mode 100644 index 000000000..9f15f3e61 --- /dev/null +++ b/spring-session-boot/src/main/java/org/springframework/session/endpoint/EndpointConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.endpoint; + +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.FindByIndexNameSessionRepository; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for session management + * {@link Endpoint}s. + * + * @author Eddú Meléndez + * @since 1.3.0 + */ +@Configuration +@ConditionalOnBean(FindByIndexNameSessionRepository.class) +public class EndpointConfiguration { + + @Bean + public SessionEndpoint sessionEndpoint() { + return new SessionEndpoint(); + } + +} diff --git a/spring-session-boot/src/main/java/org/springframework/session/endpoint/SessionEndpoint.java b/spring-session-boot/src/main/java/org/springframework/session/endpoint/SessionEndpoint.java new file mode 100644 index 000000000..056364b41 --- /dev/null +++ b/spring-session-boot/src/main/java/org/springframework/session/endpoint/SessionEndpoint.java @@ -0,0 +1,107 @@ +/* + * Copyright 2014-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.endpoint; + +import java.util.Collection; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.actuate.endpoint.EndpointProperties; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.session.ExpiringSession; +import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * {@link MvcEndpoint} to expose actuator session. + * + * @author Eddú Meléndez + * @since 1.3.0 + */ +@ConfigurationProperties("endpoints.session") +public class SessionEndpoint implements MvcEndpoint, EnvironmentAware { + + private Environment environment; + + /** + * Endpoint URL path. + */ + private String path = "/session"; + + /** + * Enable the endpoint. + */ + private boolean enabled = true; + + /** + * Mark if the endpoint exposes sensitive information. + */ + private Boolean sensitive; + + @Autowired + private FindByIndexNameSessionRepository sessionRepository; + + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @RequestMapping(path = "/{username}", method = RequestMethod.GET) + public Collection result(@PathVariable String username) { + return this.sessionRepository.findByIndexNameAndIndexValue( + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username) + .values(); + } + + @RequestMapping(path = "/{sessionId}", method = RequestMethod.DELETE) + public void delete(@PathVariable String sessionId) { + this.sessionRepository.delete(sessionId); + } + + public void setPath(String path) { + this.path = path; + } + + public String getPath() { + return this.path; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setSensitive(Boolean sensitive) { + this.sensitive = sensitive; + } + + public boolean isSensitive() { + return EndpointProperties.isSensitive(this.environment, this.sensitive, false); + } + + public Class getEndpointType() { + return Endpoint.class; + } + +} diff --git a/spring-session-boot/src/main/resources/META-INF/spring.factories b/spring-session-boot/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..3eb384322 --- /dev/null +++ b/spring-session-boot/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=\ +org.springframework.session.endpoint.EndpointConfiguration diff --git a/spring-session-boot/src/test/java/org/springframework/session/endpoint/SessionEndpointTests.java b/spring-session-boot/src/test/java/org/springframework/session/endpoint/SessionEndpointTests.java new file mode 100644 index 000000000..db2dc18d7 --- /dev/null +++ b/spring-session-boot/src/test/java/org/springframework/session/endpoint/SessionEndpointTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2014-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.session.endpoint; + +import javax.sql.DataSource; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.jdbc.JdbcOperationsSessionRepository; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SessionEndpoint}. + * + * @author Eddú Meléndez + */ +public class SessionEndpointTests { + + private AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testCustomProperties() { + this.context.register(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + ManagementServerProperties.class, SessionAutoConfiguration.class, + EndpointConfiguration.class, EndpointWebMvcAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, "endpoints.session.path:/mysession", + "endpoints.session.sensitive:true"); + this.context.refresh(); + SessionEndpoint sessionEndpoint = this.context.getBean(SessionEndpoint.class); + assertThat(sessionEndpoint.getPath()).isEqualTo("/mysession"); + assertThat(sessionEndpoint.isSensitive()).isTrue(); + assertThat(sessionEndpoint.isEnabled()).isTrue(); + } + + @Test + public void testEndpointDisabled() { + this.context.register(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + ManagementServerProperties.class, SessionAutoConfiguration.class, + EndpointConfiguration.class, EndpointWebMvcAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, "endpoints.session.enabled:false"); + this.context.refresh(); + SessionEndpoint sessionEndpoint = this.context.getBean(SessionEndpoint.class); + assertThat(sessionEndpoint.isEnabled()).isFalse(); + } + + @Test + public void testSessionRepositoryIsPresent() { + this.context.register(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + ManagementServerProperties.class, SessionAutoConfiguration.class, + EndpointConfiguration.class, EndpointWebMvcAutoConfiguration.class); + this.context.refresh(); + FindByIndexNameSessionRepository sessionRepository = this.context + .getBean(FindByIndexNameSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + } + + @Test + public void testSessionRepositoryIsNotPresent() { + this.context.register(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + ManagementServerProperties.class, EndpointConfiguration.class, + EndpointWebMvcAutoConfiguration.class); + this.context.refresh(); + String[] sessionRepository = this.context + .getBeanNamesForType(FindByIndexNameSessionRepository.class); + assertThat(sessionRepository.length).isEqualTo(0); + } + + @Configuration + static class SessionAutoConfiguration { + + @Bean + public FindByIndexNameSessionRepository sessionRepository(DataSource dataSource, + PlatformTransactionManager transactionManager) { + return new JdbcOperationsSessionRepository(dataSource, transactionManager); + } + } + +}