diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java new file mode 100644 index 0000000000..8a3607eaae --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2018 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.boot.actuate.autoconfigure.endpoint.web; + +import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; +import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link ManagementContextConfiguration} for servlet endpoints. + * + * @author Phillip Webb + * + * @since 2.0.0 + */ +@Configuration +@ConditionalOnWebApplication(type = Type.SERVLET) +public class ServletEndpointManagementContextConfiguration { + + @Bean + public ServletEndpointRegistrar servletEndpointRegistrar( + WebEndpointProperties properties, + ServletEndpointsSupplier servletEndpointsSupplier) { + return new ServletEndpointRegistrar(properties.getBasePath(), + servletEndpointsSupplier.getEndpoints()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java index 80ecda4572..ecdcaff1b3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java @@ -32,6 +32,7 @@ import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.PathMapper; @@ -39,12 +40,15 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -138,4 +142,20 @@ public class WebEndpointAutoConfiguration { ExposableControllerEndpoint.class, expose, exclude); } + @ConditionalOnWebApplication(type = Type.SERVLET) + static class WebEndpointServletAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(ServletEndpointsSupplier.class) + public ServletEndpointDiscoverer servletEndpointDiscoverer( + ApplicationContext applicationContext, PathMapper webEndpointPathMapper, + ObjectProvider> invokerAdvisors, + ObjectProvider>> filters) { + return new ServletEndpointDiscoverer(applicationContext, + webEndpointPathMapper, + filters.getIfAvailable(Collections::emptyList)); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpoint.java new file mode 100644 index 0000000000..49c23893fb --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpoint.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2018 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.boot.actuate.autoconfigure.jolokia; + +import java.util.Map; +import java.util.function.Supplier; + +import org.jolokia.http.AgentServlet; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.web.EndpointServlet; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; + +/** + * {@link Endpoint} to expose a Jolokia {@link AgentServlet}. + * + * @author Phillip Webb + * @since 2.0.0 + */ +@ServletEndpoint(id = "jolokia") +public class JolokiaEndpoint implements Supplier { + + private Map initParameters; + + public JolokiaEndpoint(Map initParameters) { + this.initParameters = initParameters; + } + + @Override + public EndpointServlet get() { + return new EndpointServlet(AgentServlet.class) + .withInitParameters(this.initParameters); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpointAutoConfiguration.java new file mode 100644 index 0000000000..cb42ebd6df --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpointAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2018 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.boot.actuate.autoconfigure.jolokia; + +import org.jolokia.http.AgentServlet; + +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for the {@link JolokiaEndpoint}. + * + * @author Phillip Webb + * @since 2.0.0 + */ +@Configuration +@ConditionalOnWebApplication(type = Type.SERVLET) +@ConditionalOnClass(AgentServlet.class) +@EnableConfigurationProperties(JolokiaProperties.class) +public class JolokiaEndpointAutoConfiguration { + + @Bean + @ConditionalOnEnabledEndpoint + public JolokiaEndpoint jolokiaEndpoint(JolokiaProperties properties) { + return new JolokiaEndpoint(properties.getConfig()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfiguration.java deleted file mode 100644 index fc1cbc0227..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfiguration.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2012-2017 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.boot.actuate.autoconfigure.jolokia; - -import org.jolokia.http.AgentServlet; - -import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; -import org.springframework.boot.actuate.autoconfigure.web.servlet.ManagementServletContext; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.web.servlet.mvc.ServletWrappingController; - -/** - * {@link ManagementContextConfiguration} for embedding Jolokia, a JMX-HTTP bridge giving - * an alternative to JSR-160 connectors. - *

- * This configuration will get automatically enabled as soon as the Jolokia - * {@link AgentServlet} is on the classpath. To disable it set - * {@code management.jolokia.enabled=false}. - *

- * Additional configuration parameters for Jolokia can be provided by specifying - * {@code management.jolokia.config.*} properties. See the - * http://jolokia.org web site for more information on - * supported configuration parameters. - * - * @author Christian Dupuis - * @author Dave Syer - * @author Andy Wilkinson - * @author Madhura Bhave - * @author Stephane Nicoll - * @since 2.0.0 - */ -@ManagementContextConfiguration -@ConditionalOnWebApplication(type = Type.SERVLET) -@ConditionalOnClass({ AgentServlet.class, ServletWrappingController.class }) -@ConditionalOnProperty(value = "management.jolokia.enabled", havingValue = "true") -@EnableConfigurationProperties(JolokiaProperties.class) -public class JolokiaManagementContextConfiguration { - - private final ManagementServletContext managementServletContext; - - private final JolokiaProperties properties; - - public JolokiaManagementContextConfiguration( - ManagementServletContext managementServletContext, - JolokiaProperties properties) { - this.managementServletContext = managementServletContext; - this.properties = properties; - } - - @Bean - public ServletRegistrationBean jolokiaServlet() { - String path = this.managementServletContext.getServletPath() - + this.properties.getPath(); - String urlMapping = (path.endsWith("/") ? path + "*" : path + "/*"); - ServletRegistrationBean registration = new ServletRegistrationBean<>( - new AgentServlet(), urlMapping); - registration.setInitParameters(this.properties.getConfig()); - return registration; - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaProperties.java index 7768a07ccc..4feb167925 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -29,41 +29,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Stephane Nicoll * @since 2.0.0 */ -@ConfigurationProperties(prefix = "management.jolokia") +@ConfigurationProperties(prefix = "management.endpoint.jolokia") public class JolokiaProperties { - /** - * Whether to enable Jolokia. - */ - private boolean enabled; - - /** - * Path at which Jolokia is available. - */ - private String path = "/jolokia"; - /** * Jolokia settings. These are traditionally set using servlet parameters. Refer to * the documentation of Jolokia for more details. */ private final Map config = new HashMap<>(); - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getPath() { - return this.path; - } - - public void setPath(String path) { - this.path = path; - } - public Map getConfig() { return this.config; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index f10eccc930..ee46ffeb56 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -795,7 +795,7 @@ "type": "java.lang.String", "description": "Endpoint URL path.", "deprecation": { - "reason": "Endpoint path is no longer customizable.", + "replacement": "management.endpoints.web.path-mapping.jolokia", "level": "error" } }, @@ -1734,4 +1734,3 @@ } ] } - diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index b14ec5473c..43a4e37b4c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -23,6 +23,7 @@ org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfigura org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.jms.JmsHealthIndicatorAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ldap.LdapHealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.liquibase.LiquibaseEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.logging.LogFileWebEndpointAutoConfiguration,\ @@ -47,10 +48,10 @@ org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoC org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=\ +org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration,\ -org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.web.jersey.JerseyManagementChildContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementChildContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementChildContextConfiguration,\ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java new file mode 100644 index 0000000000..f9cd636a77 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2018 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.boot.actuate.autoconfigure.endpoint.web; + +import java.util.Collections; + +import org.junit.Test; + +import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ServletEndpointManagementContextConfiguration}. + * + * @author Phillip Webb + */ +public class ServletEndpointManagementContextConfigurationTests { + + private WebApplicationContextRunner runner = new WebApplicationContextRunner() + .withUserConfiguration(TestConfig.class); + + @Test + public void contextShouldContainServletEndpointRegistrar() { + this.runner.run((context) -> assertThat(context) + .hasSingleBean(ServletEndpointRegistrar.class)); + } + + @Test + public void contextWhenNoServletBasedShouldNotContainServletEndpointRegistrar() { + new ApplicationContextRunner().withUserConfiguration(TestConfig.class) + .run((context) -> assertThat(context) + .doesNotHaveBean(ServletEndpointRegistrar.class)); + } + + @Configuration + @Import(ServletEndpointManagementContextConfiguration.class) + @EnableConfigurationProperties(WebEndpointProperties.class) + static class TestConfig { + + @Bean + public ServletEndpointsSupplier servletEndpointsSupplier() { + return () -> Collections.emptyList(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java index ae70f47e5d..0fd938278f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java @@ -24,8 +24,10 @@ import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointDiscoverer; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -35,12 +37,15 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson * @author Yunkun Huang + * @author Phillip Webb */ public class WebEndpointAutoConfigurationTests { + private static final AutoConfigurations CONFIGURATIONS = AutoConfigurations + .of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class); + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class, - WebEndpointAutoConfiguration.class)); + .withConfiguration(CONFIGURATIONS); @Test public void webApplicationConfiguresEndpointMediaTypes() { @@ -82,4 +87,18 @@ public class WebEndpointAutoConfigurationTests { }); } + @Test + public void contextShouldConfigureServletEndpointDiscoverer() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(ServletEndpointDiscoverer.class); + }); + } + + @Test + public void contextWhenNotServletShouldNotConfigureServletEndpointDiscoverer() { + new ApplicationContextRunner().withConfiguration(CONFIGURATIONS) + .run((context) -> assertThat(context) + .doesNotHaveBean(ServletEndpointDiscoverer.class)); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JolokiaManagementContextConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JolokiaManagementContextConfigurationIntegrationTests.java index 0d4ff0d208..c6ca06f0fd 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JolokiaManagementContextConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JolokiaManagementContextConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -27,8 +27,10 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration; +import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaEndpoint; +import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -53,14 +55,14 @@ import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link JolokiaManagementContextConfiguration}. + * Integration tests for {@link JolokiaEndpoint}. * * @author Stephane Nicoll */ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @DirtiesContext -@TestPropertySource(properties = "management.jolokia.enabled=true") +@TestPropertySource(properties = "management.endpoints.web.expose=jolokia") public class JolokiaManagementContextConfigurationIntegrationTests { @Autowired @@ -70,7 +72,7 @@ public class JolokiaManagementContextConfigurationIntegrationTests { public void jolokiaIsExposed() { ResponseEntity response = this.restTemplate .getForEntity("/actuator/jolokia", String.class); - assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains("\"agent\""); assertThat(response.getBody()).contains("\"request\":{\"type\""); } @@ -79,7 +81,7 @@ public class JolokiaManagementContextConfigurationIntegrationTests { public void search() { ResponseEntity response = this.restTemplate .getForEntity("/actuator/jolokia/search/java.lang:*", String.class); - assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains("GarbageCollector"); } @@ -87,7 +89,7 @@ public class JolokiaManagementContextConfigurationIntegrationTests { public void read() { ResponseEntity response = this.restTemplate.getForEntity( "/actuator/jolokia/read/java.lang:type=Memory", String.class); - assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains("NonHeapMemoryUsage"); } @@ -95,18 +97,19 @@ public class JolokiaManagementContextConfigurationIntegrationTests { public void list() { ResponseEntity response = this.restTemplate.getForEntity( "/actuator/jolokia/list/java.lang/type=Memory/attr", String.class); - assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains("NonHeapMemoryUsage"); } @Configuration @MinimalWebConfiguration @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + JolokiaEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class, - ServletManagementContextAutoConfiguration.class }) + ServletEndpointManagementContextConfiguration.class }) protected static class Application { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpointAutoConfigurationTests.java new file mode 100644 index 0000000000..9c89184048 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaEndpointAutoConfigurationTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2012-2018 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.boot.actuate.autoconfigure.jolokia; + +import java.util.Collection; +import java.util.Collections; + +import org.jolokia.http.AgentServlet; +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; +import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; +import org.springframework.boot.actuate.endpoint.web.PathMapper; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointDiscoverer; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JolokiaEndpointAutoConfiguration}. + * + * @author Christian Dupuis + * @author Andy Wilkinson + * @author Stephane Nicoll + */ +public class JolokiaEndpointAutoConfigurationTests { + + private final WebApplicationContextRunner runner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + ManagementContextAutoConfiguration.class, + ServletManagementContextAutoConfiguration.class, + ServletEndpointManagementContextConfiguration.class, + JolokiaEndpointAutoConfiguration.class, TestConfiguration.class)); + + @Test + public void jolokiaServletShouldBeEnabledByDefault() { + this.runner.run((context) -> { + ExposableServletEndpoint endpoint = getEndpoint(context); + assertThat(endpoint.getRootPath()).isEqualTo("jolokia"); + Object servlet = ReflectionTestUtils.getField(endpoint.getEndpointServlet(), + "servlet"); + assertThat(servlet).isInstanceOf(AgentServlet.class); + }); + } + + @Test + public void jolokiaServletWhenDisabledShouldNotBeDiscovered() { + this.runner.withPropertyValues("management.endpoint.jolokia.enabled=false") + .run((context) -> { + Collection endpoints = context + .getBean(ServletEndpointsSupplier.class).getEndpoints(); + assertThat(endpoints).isEmpty(); + }); + } + + @Test + public void jolokiaServletWhenHasCustomConfigShouldApplyInitParams() { + this.runner.withPropertyValues("management.endpoint.jolokia.config.debug=true") + .run((context) -> { + ExposableServletEndpoint endpoint = getEndpoint(context); + assertThat(endpoint.getEndpointServlet()).extracting("initParameters") + .containsOnly(Collections.singletonMap("debug", "true")); + }); + } + + private ExposableServletEndpoint getEndpoint( + AssertableWebApplicationContext context) { + Collection endpoints = context + .getBean(ServletEndpointsSupplier.class).getEndpoints(); + return endpoints.iterator().next(); + } + + @Configuration + static class TestConfiguration { + + @Bean + public ServletEndpointDiscoverer servletEndpointDiscoverer( + ApplicationContext applicationContext) { + return new ServletEndpointDiscoverer(applicationContext, + PathMapper.useEndpointId(), Collections.emptyList()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java deleted file mode 100644 index 1ab4d9b37b..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2012-2017 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.boot.actuate.autoconfigure.jolokia; - -import org.junit.Test; - -import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; -import org.springframework.boot.test.context.runner.ContextConsumer; -import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.boot.web.servlet.ServletRegistrationBean; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - -/** - * Tests for {@link JolokiaManagementContextConfiguration}. - * - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Stephane Nicoll - */ -public class JolokiaManagementContextConfigurationTests { - - private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of(ManagementContextAutoConfiguration.class, - ServletManagementContextAutoConfiguration.class, - JolokiaManagementContextConfiguration.class)); - - @Test - public void jolokiaCanBeEnabled() { - this.contextRunner.withPropertyValues("management.jolokia.enabled=true") - .run((context) -> { - context.getBean(ServletRegistrationBean.class); - assertThat(context).hasSingleBean(ServletRegistrationBean.class); - ServletRegistrationBean registrationBean = context - .getBean(ServletRegistrationBean.class); - assertThat(registrationBean.getUrlMappings()) - .contains("/actuator/jolokia/*"); - assertThat(registrationBean.getInitParameters()).isEmpty(); - }); - } - - @Test - public void jolokiaIsDisabledByDefault() { - this.contextRunner.run((context) -> assertThat(context) - .doesNotHaveBean(ServletRegistrationBean.class)); - } - - @Test - public void customPath() { - this.contextRunner - .withPropertyValues("management.jolokia.enabled=true", - "management.jolokia.path=/lokia") - .run(isDefinedOnPath("/actuator/lokia/*")); - } - - @Test - public void customManagementPath() { - this.contextRunner - .withPropertyValues("management.jolokia.enabled=true", - "management.endpoints.web.base-path=/admin") - .run(isDefinedOnPath("/admin/jolokia/*")); - } - - @Test - public void customInitParameters() { - this.contextRunner.withPropertyValues("management.jolokia.enabled=true", - "management.jolokia.config.debug=true").run((context) -> { - assertThat(context).hasSingleBean(ServletRegistrationBean.class); - ServletRegistrationBean registrationBean = context - .getBean(ServletRegistrationBean.class); - assertThat(registrationBean.getInitParameters()) - .containsOnly(entry("debug", "true")); - }); - } - - private ContextConsumer isDefinedOnPath( - String path) { - return (context) -> { - assertThat(context).hasSingleBean(ServletRegistrationBean.class); - ServletRegistrationBean registrationBean = context - .getBean(ServletRegistrationBean.class); - assertThat(registrationBean.getUrlMappings()).containsExactly(path); - }; - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java index 94d08d5451..674b64b0ee 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java @@ -291,6 +291,9 @@ public abstract class EndpointDiscoverer, O exten @SuppressWarnings("unchecked") private boolean isFilterMatch(Class filter, EndpointBean endpointBean) { + if (!isEndpointExposed(endpointBean.getBean())) { + return false; + } if (filter == null) { return true; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/ParameterValueMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/ParameterValueMapper.java index 16414c6636..28fca89ad2 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/ParameterValueMapper.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/ParameterValueMapper.java @@ -25,6 +25,11 @@ package org.springframework.boot.actuate.endpoint.invoke; @FunctionalInterface public interface ParameterValueMapper { + /** + * A {@link ParameterValueMapper} that does nothing. + */ + ParameterValueMapper NONE = (paramere, value) -> value; + /** * Map the specified {@code input} parameter to the given {@code parameterType}. * @param parameter the parameter to map diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointServlet.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointServlet.java new file mode 100644 index 0000000000..1731d6cb8c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointServlet.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.servlet.Servlet; + +import org.springframework.beans.BeanUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Contains details of a servlet that is exposed as an actuator endpoint. + * + * @author Phillip Webb + */ +public final class EndpointServlet { + + private final Servlet servlet; + + private final Map initParameters; + + public EndpointServlet(Class servlet) { + Assert.notNull(servlet, "Servlet must not be null"); + this.servlet = BeanUtils.instantiateClass(servlet); + this.initParameters = Collections.emptyMap(); + } + + public EndpointServlet(Servlet servlet) { + Assert.notNull(servlet, "Servlet must not be null"); + this.servlet = servlet; + this.initParameters = Collections.emptyMap(); + } + + private EndpointServlet(Servlet servlet, Map initParameters) { + this.servlet = servlet; + this.initParameters = Collections.unmodifiableMap(initParameters); + } + + public EndpointServlet withInitParameter(String name, String value) { + Assert.hasText(name, "Name must not be empty"); + return withInitParameters(Collections.singletonMap(name, value)); + } + + public EndpointServlet withInitParameters(Map initParameters) { + Assert.notNull(initParameters, "InitParameters must not be null"); + boolean hasEmptyKey = initParameters.values().stream() + .anyMatch((key) -> !StringUtils.hasText(key)); + Assert.isTrue(!hasEmptyKey, "InitParameters must not contain empty keys"); + Map mergedInitParameters = new LinkedHashMap<>( + this.initParameters); + mergedInitParameters.putAll(initParameters); + return new EndpointServlet(this.servlet, mergedInitParameters); + } + + Servlet getServlet() { + return this.servlet; + } + + Map getInitParameters() { + return this.initParameters; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ExposableServletEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ExposableServletEndpoint.java new file mode 100644 index 0000000000..6457ca088b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ExposableServletEndpoint.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web; + +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.actuate.endpoint.Operation; + +/** + * Information describing an endpoint that can be exposed by registering a servlet. + * + * @author Phillip Webb + * @since 2.0.0 + */ +public interface ExposableServletEndpoint + extends ExposableEndpoint, PathMappedEndpoint { + + /** + * Return details of the servlet that should registered. + * @return the endpoint servlet + */ + EndpointServlet getEndpointServlet(); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java new file mode 100644 index 0000000000..ac3c8fae3a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web; + +import java.util.Collection; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration.Dynamic; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.util.Assert; + +/** + * {@link ServletContextInitializer} to register {@link ExposableServletEndpoint} servlet + * endpoints. + * + * @author Phillip Webb + * @since 2.0.0 + */ +public class ServletEndpointRegistrar implements ServletContextInitializer { + + private static final Log logger = LogFactory.getLog(ServletEndpointRegistrar.class); + + private final String basePath; + + private final Collection servletEndpoints; + + public ServletEndpointRegistrar(String basePath, + Collection servletEndpoints) { + Assert.notNull(servletEndpoints, "ServletEndpoints must not be null"); + this.basePath = (basePath == null ? "" : basePath); + this.servletEndpoints = servletEndpoints; + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + this.servletEndpoints.forEach((servletEndpoint) -> { + register(servletContext, servletEndpoint); + }); + } + + private void register(ServletContext servletContext, + ExposableServletEndpoint endpoint) { + String name = endpoint.getId() + "-actuator-endpoint"; + String path = this.basePath + "/" + endpoint.getRootPath(); + String urlMapping = (path.endsWith("/") ? path + "*" : path + "/*"); + EndpointServlet endpointServlet = endpoint.getEndpointServlet(); + Dynamic registration = servletContext.addServlet(name, + endpointServlet.getServlet()); + registration.addMapping(urlMapping); + registration.setInitParameters(endpointServlet.getInitParameters()); + logger.info("Registered '" + path + "' to " + name); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java index 80747421a1..dcef55afd1 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java @@ -25,8 +25,6 @@ import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; -import org.springframework.boot.actuate.endpoint.invoke.OperationParameter; -import org.springframework.boot.actuate.endpoint.invoke.ParameterMappingException; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.context.ApplicationContext; @@ -54,7 +52,7 @@ public class ControllerEndpointDiscoverer public ControllerEndpointDiscoverer(ApplicationContext applicationContext, PathMapper endpointPathMapper, Collection> filters) { - super(applicationContext, new NoOpParameterValueMapper(), Collections.emptyList(), + super(applicationContext, ParameterValueMapper.NONE, Collections.emptyList(), filters); Assert.notNull(endpointPathMapper, "EndpointPathMapper must not be null"); this.endpointPathMapper = endpointPathMapper; @@ -88,17 +86,4 @@ public class ControllerEndpointDiscoverer "ControllerEndpoints must not declare operations"); } - /** - * {@link ParameterValueMapper} that does nothing. - */ - private static class NoOpParameterValueMapper implements ParameterValueMapper { - - @Override - public Object mapParameterValue(OperationParameter parameter, Object value) - throws ParameterMappingException { - return value; - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java index da5b0b3518..550386af4e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java @@ -18,13 +18,12 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.util.Collections; -import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; /** - * A discovered {@link ExposableEndpoint controller endpoint}. + * A discovered {@link ExposableControllerEndpoint controller endpoint}. * * @author Phillip Webb */ diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java new file mode 100644 index 0000000000..dd9c89fac5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web.annotation; + +import java.util.Collections; +import java.util.function.Supplier; + +import org.springframework.boot.actuate.endpoint.Operation; +import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; +import org.springframework.boot.actuate.endpoint.web.EndpointServlet; +import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; +import org.springframework.util.Assert; + +/** + * A discovered {@link ExposableServletEndpoint controller endpoint}. + * + * @author Phillip Webb + */ +class DiscoveredServletEndpoint extends AbstractDiscoveredEndpoint + implements ExposableServletEndpoint { + + private final String rootPath; + + private final EndpointServlet endpointServlet; + + DiscoveredServletEndpoint(EndpointDiscoverer discoverer, Object endpointBean, + String id, String rootPath, boolean enabledByDefault) { + super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList()); + String beanType = endpointBean.getClass().getName(); + Assert.state(endpointBean instanceof Supplier, + () -> "ServletEndpoint bean " + beanType + " must be a supplier"); + Object supplied = ((Supplier) endpointBean).get(); + Assert.state(supplied != null, + "ServletEndpoint bean " + beanType + " must not supply null"); + Assert.state(supplied instanceof EndpointServlet, () -> "ServletEndpoint bean " + + beanType + " must supply an EndpointServlet"); + this.endpointServlet = (EndpointServlet) supplied; + this.rootPath = rootPath; + } + + @Override + public String getRootPath() { + return this.rootPath; + } + + @Override + public EndpointServlet getEndpointServlet() { + return this.endpointServlet; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpoint.java new file mode 100644 index 0000000000..3a6792b368 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpoint.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.Supplier; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint; +import org.springframework.boot.actuate.endpoint.web.EndpointServlet; +import org.springframework.core.annotation.AliasFor; + +/** + * Identifies a type as being an endpoint that supplies a servlet to expose. + * Implementations must also implement {@link Supplier Supplier<EndpointServlet>} + * and return a valid {@link EndpointServlet}. + *

+ * This annotation can be used when existing servlets need be be exposed as actuator + * endpoints, but it is at the expense of portability. Most users should prefer the + * {@link Endpoint @Endpoint} or {@link WebEndpoint @WebEndpoint} annotations whenever + * possible. + * + * @author Phillip Webb + * @since 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Endpoint +@FilteredEndpoint(ServletEndpointFilter.class) +public @interface ServletEndpoint { + + /** + * The id of the endpoint. + * @return the id + */ + @AliasFor(annotation = Endpoint.class) + String id(); + + /** + * If the endpoint should be enabled or disabled by default. + * @return {@code true} if the endpoint is enabled by default + */ + @AliasFor(annotation = Endpoint.class) + boolean enableByDefault() default true; + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java new file mode 100644 index 0000000000..a8b3405c1c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web.annotation; + +import java.util.Collection; +import java.util.Collections; + +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.Operation; +import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; +import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; +import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; +import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; +import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; +import org.springframework.boot.actuate.endpoint.web.PathMapper; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.util.Assert; + +/** + * {@link EndpointDiscoverer} for {@link ExposableServletEndpoint servlet endpoints}. + * + * @author Phillip Webb + * @since 2.0.0 + */ +public class ServletEndpointDiscoverer + extends EndpointDiscoverer + implements ServletEndpointsSupplier { + + private final PathMapper endpointPathMapper; + + /** + * Create a new {@link ServletEndpointFilter} instance. + * @param applicationContext the source application context + * @param endpointPathMapper the endpoint path mapper + * @param filters filters to apply + */ + public ServletEndpointDiscoverer(ApplicationContext applicationContext, + PathMapper endpointPathMapper, + Collection> filters) { + super(applicationContext, ParameterValueMapper.NONE, Collections.emptyList(), + filters); + Assert.notNull(endpointPathMapper, "EndpointPathMapper must not be null"); + this.endpointPathMapper = endpointPathMapper; + } + + @Override + protected boolean isEndpointExposed(Object endpointBean) { + Class type = endpointBean.getClass(); + return AnnotatedElementUtils.isAnnotated(type, ServletEndpoint.class); + } + + @Override + protected ExposableServletEndpoint createEndpoint(Object endpointBean, String id, + boolean enabledByDefault, Collection operations) { + String rootPath = this.endpointPathMapper.getRootPath(id); + return new DiscoveredServletEndpoint(this, endpointBean, id, rootPath, + enabledByDefault); + } + + @Override + protected Operation createOperation(String endpointId, + DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { + throw new IllegalStateException("ServletEndpoints must not declare operations"); + } + + @Override + protected OperationKey createOperationKey(Operation operation) { + throw new IllegalStateException("ServletEndpoints must not declare operations"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointFilter.java new file mode 100644 index 0000000000..4e77e26371 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointFilter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web.annotation; + +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.annotation.DiscovererEndpointFilter; + +/** + * {@link EndpointFilter} for endpoints discovered by {@link ServletEndpointDiscoverer}. + * + * @author Phillip Webb + */ +class ServletEndpointFilter extends DiscovererEndpointFilter { + + ServletEndpointFilter() { + super(ServletEndpointDiscoverer.class); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointsSupplier.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointsSupplier.java new file mode 100644 index 0000000000..1e8b456083 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointsSupplier.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web.annotation; + +import org.springframework.boot.actuate.endpoint.EndpointsSupplier; +import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; + +/** + * {@link EndpointsSupplier} for {@link ExposableServletEndpoint servlet endpoints}. + * + * @author Phillip Webb + * @since 2.0.0 + */ +@FunctionalInterface +public interface ServletEndpointsSupplier + extends EndpointsSupplier { + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointServletTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointServletTests.java new file mode 100644 index 0000000000..ee26b811fd --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointServletTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.servlet.GenericServlet; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * Tests for {@link EndpointServlet}. + * + * @author Phillip Webb + */ +public class EndpointServletTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void createWhenServletClassIsNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Servlet must not be null"); + new EndpointServlet((Class) null); + } + + @Test + public void createWhenServletIsNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Servlet must not be null"); + new EndpointServlet((Servlet) null); + } + + @Test + public void createWithServletClassShouldCreateServletInstance() { + EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class); + assertThat(endpointServlet.getServlet()).isInstanceOf(TestServlet.class); + } + + @Test + public void getServletShouldGetServlet() { + TestServlet servlet = new TestServlet(); + EndpointServlet endpointServlet = new EndpointServlet(servlet); + assertThat(endpointServlet.getServlet()).isEqualTo(servlet); + } + + @Test + public void withInitParameterShouldReturnNewInstance() { + EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class); + assertThat(endpointServlet.withInitParameter("spring", "boot")) + .isNotSameAs(endpointServlet); + } + + @Test + public void withInitParameterWhenHasExistingShouldMergeParameters() { + EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class) + .withInitParameter("a", "b").withInitParameter("c", "d"); + assertThat(endpointServlet.withInitParameter("a", "b1") + .withInitParameter("e", "f").getInitParameters()).containsExactly( + entry("a", "b1"), entry("c", "d"), entry("e", "f")); + } + + @Test + public void withInitParametersShouldCreateNewInstance() { + EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class); + assertThat(endpointServlet + .withInitParameters(Collections.singletonMap("spring", "boot"))) + .isNotSameAs(endpointServlet); + } + + @Test + public void withInitParametersWhenHasExistingShouldMergeParameters() { + EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class) + .withInitParameter("a", "b").withInitParameter("c", "d"); + Map extra = new LinkedHashMap<>(); + extra.put("a", "b1"); + extra.put("e", "f"); + assertThat(endpointServlet.withInitParameters(extra).getInitParameters()) + .containsExactly(entry("a", "b1"), entry("c", "d"), entry("e", "f")); + + } + + private static class TestServlet extends GenericServlet { + + @Override + public void service(ServletRequest req, ServletResponse res) + throws ServletException, IOException { + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java new file mode 100644 index 0000000000..542f435bc2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web; + +import java.io.IOException; +import java.util.Collections; + +import javax.servlet.GenericServlet; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration.Dynamic; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link ServletEndpointRegistrar}. + * + * @author Phillip Webb + */ +public class ServletEndpointRegistrarTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Mock + private ServletContext servletContext; + + @Mock + private Dynamic dynamic; + + @Captor + private ArgumentCaptor servlet; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + given(this.servletContext.addServlet(any(String.class), any(Servlet.class))) + .willReturn(this.dynamic); + } + + @Test + public void createWhenServletEndpointsIsNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("ServletEndpoints must not be null"); + new ServletEndpointRegistrar(null, null); + } + + @Test + public void onStartupShouldRegisterServlets() throws Exception { + ExposableServletEndpoint endpoint = mockEndpoint( + new EndpointServlet(TestServlet.class)); + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(null, + Collections.singleton(endpoint)); + registrar.onStartup(this.servletContext); + verify(this.servletContext).addServlet(eq("test-actuator-endpoint"), + this.servlet.capture()); + assertThat(this.servlet.getValue()).isInstanceOf(TestServlet.class); + verify(this.dynamic).addMapping("/test/*"); + } + + @Test + public void onStartupWhenHasBasePathShouldIncludeBasePath() throws Exception { + ExposableServletEndpoint endpoint = mockEndpoint( + new EndpointServlet(TestServlet.class)); + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", + Collections.singleton(endpoint)); + registrar.onStartup(this.servletContext); + verify(this.servletContext).addServlet(eq("test-actuator-endpoint"), + this.servlet.capture()); + assertThat(this.servlet.getValue()).isInstanceOf(TestServlet.class); + verify(this.dynamic).addMapping("/actuator/test/*"); + } + + @Test + public void onStartupWhenHasInitParametersShouldRegisterInitParameters() + throws Exception { + ExposableServletEndpoint endpoint = mockEndpoint( + new EndpointServlet(TestServlet.class).withInitParameter("a", "b")); + ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator", + Collections.singleton(endpoint)); + registrar.onStartup(this.servletContext); + verify(this.dynamic).setInitParameters(Collections.singletonMap("a", "b")); + } + + private ExposableServletEndpoint mockEndpoint(EndpointServlet endpointServlet) { + ExposableServletEndpoint endpoint = mock(ExposableServletEndpoint.class); + given(endpoint.getId()).willReturn("test"); + given(endpoint.getEndpointServlet()).willReturn(endpointServlet); + given(endpoint.getRootPath()).willReturn("test"); + return endpoint; + } + + public static class TestServlet extends GenericServlet { + + @Override + public void service(ServletRequest req, ServletResponse res) + throws ServletException, IOException { + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java index 00eabebac7..b29ad955ad 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.web.PathMapper; @@ -61,6 +62,7 @@ public class ControllerEndpointDiscovererTests { assertThat(endpoint.getId()).isEqualTo("testcontroller"); assertThat(endpoint.getController()) .isInstanceOf(TestControllerEndpoint.class); + assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); }); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java new file mode 100644 index 0000000000..99b52d5b95 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java @@ -0,0 +1,212 @@ +/* + * Copyright 2012-2018 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.boot.actuate.endpoint.web.annotation; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.servlet.GenericServlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.web.EndpointServlet; +import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; +import org.springframework.boot.actuate.endpoint.web.PathMapper; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ServletEndpointDiscoverer}. + * + * @author Phillip Webb + */ +public class ServletEndpointDiscovererTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Test + public void getEndpointsWhenNoEndpointBeansShouldReturnEmptyCollection() { + load(EmptyConfiguration.class, + (discoverer) -> assertThat(discoverer.getEndpoints()).isEmpty()); + } + + @Test + public void getEndpointsShouldIncludeServletEndpoints() { + load(TestServletEndpoint.class, (discoverer) -> { + Collection endpoints = discoverer.getEndpoints(); + assertThat(endpoints).hasSize(1); + ExposableServletEndpoint endpoint = endpoints.iterator().next(); + assertThat(endpoint.getId()).isEqualTo("testservlet"); + assertThat(endpoint.getEndpointServlet()).isNotNull(); + assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); + }); + } + + @Test + public void getEndpointsShouldNotDiscoverRegularEndpoints() { + load(WithRegularEndpointConfiguration.class, (discoverer) -> { + Collection endpoints = discoverer.getEndpoints(); + List ids = endpoints.stream().map(ExposableEndpoint::getId) + .collect(Collectors.toList()); + assertThat(ids).containsOnly("testservlet"); + }); + } + + @Test + public void getEndpointWhenEndpointHasOperationsShouldThrowException() { + load(TestServletEndpointWithOperation.class, (discoverer) -> { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("ServletEndpoints must not declare operations"); + discoverer.getEndpoints(); + }); + } + + @Test + public void getEndpointWhenEndpointNotASupplierShouldThrowException() { + load(TestServletEndpointNotASupplier.class, (discoverer) -> { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("must be a supplier"); + discoverer.getEndpoints(); + }); + } + + @Test + public void getEndpointWhenEndpointSuppliesWrongTypeShouldThrowException() { + load(TestServletEndpointSupplierOfWrongType.class, (discoverer) -> { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("must supply an EndpointServlet"); + discoverer.getEndpoints(); + }); + } + + @Test + public void getEndpointWhenEndpoinSuppliesNullShouldThrowException() { + load(TestServletEndpointSupplierOfNull.class, (discoverer) -> { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("must not supply null"); + discoverer.getEndpoints(); + }); + } + + private void load(Class configuration, + Consumer consumer) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + configuration); + try { + ServletEndpointDiscoverer discoverer = new ServletEndpointDiscoverer(context, + PathMapper.useEndpointId(), Collections.emptyList()); + consumer.accept(discoverer); + } + finally { + context.close(); + } + } + + @Configuration + static class EmptyConfiguration { + + } + + @Configuration + @Import({ TestEndpoint.class, TestServletEndpoint.class }) + static class WithRegularEndpointConfiguration { + + } + + @ServletEndpoint(id = "testservlet") + static class TestServletEndpoint implements Supplier { + + @Override + public EndpointServlet get() { + return new EndpointServlet(TestServlet.class); + } + + } + + @Endpoint(id = "test") + static class TestEndpoint { + + } + + @ServletEndpoint(id = "testservlet") + static class TestServletEndpointWithOperation implements Supplier { + + @Override + public EndpointServlet get() { + return new EndpointServlet(TestServlet.class); + } + + @ReadOperation + public String read() { + return "error"; + } + + } + + private static class TestServlet extends GenericServlet { + + @Override + public void service(ServletRequest req, ServletResponse res) + throws ServletException, IOException { + } + + } + + @ServletEndpoint(id = "testservlet") + static class TestServletEndpointNotASupplier { + + } + + @ServletEndpoint(id = "testservlet") + static class TestServletEndpointSupplierOfWrongType implements Supplier { + + @Override + public String get() { + return "error"; + } + + } + + @ServletEndpoint(id = "testservlet") + static class TestServletEndpointSupplierOfNull implements Supplier { + + @Override + public EndpointServlet get() { + return null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index c722206a4b..ebfff8d841 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1258,6 +1258,10 @@ content into your application. Rather, pick only the properties that you need. management.endpoint.trace.cache.time-to-live=0ms # Maximum time that a response can be cached. management.endpoint.trace.enabled= # Whether to enable the trace endpoint. + # JOLOKIA ENDPOINT ({sc-spring-boot-actuator-autoconfigure}/jolokia/JolokiaProperties.{sc-ext}[JolokiaProperties]) + management.endpoint.jolokia.config.*= # Jolokia settings. See the Jolokia manual for details. + management.endpoint.jolokia.enabled=true # Whether to enable Jolokia. + # HEALTH INDICATORS management.health.db.enabled=true # Whether to enable database health check. management.health.cassandra.enabled=true # Whether to enable Cassandra health check. @@ -1288,11 +1292,6 @@ content into your application. Rather, pick only the properties that you need. management.info.git.enabled=true # Whether to enable git info. management.info.git.mode=simple # Mode to use to expose git information. - # JOLOKIA ({sc-spring-boot-actuator-autoconfigure}/jolokia/JolokiaProperties.{sc-ext}[JolokiaProperties]) - management.jolokia.config.*= # Jolokia settings. See the Jolokia manual for details. - management.jolokia.enabled=false # Whether to enable Jolokia. - management.jolokia.path=/jolokia # Path at which Jolokia is available. - # METRICS management.metrics.binders.jvm.enabled=true # Whether to enable JVM metrics. management.metrics.binders.logback.enabled=true # Whether to enable Logback metrics. diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 91d1f91742..67f8a5da7e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -136,8 +136,13 @@ content. |`prometheus` |Exposes metrics in a format that can be scraped by a Prometheus server. + +|`jolokia` +|Exposes JMX beans over HTTP (when Jolokia is on the classpath, not availble for WebFlux). + |=== + To learn more about the Actuator's endpoints and their request and response formats, please refer to the separate API documentation ({spring-boot-actuator-api}/html[HTML] or {spring-boot-actuator-api}/pdf/spring-boot-actuator-web-api.pdf[PDF]). @@ -857,8 +862,9 @@ Maven, you would add the following dependency: ---- -Jolokia can then be accessed by using `/actuator/jolokia` on your management HTTP -server. +The Jolokia endpoint can then be exposed by adding `jolokia` or `*` to the +`management.endpoints.web.expose` property. You can then be accessed it by using +`/actuator/jolokia` on your management HTTP server. @@ -866,11 +872,12 @@ server. ==== Customizing Jolokia Jolokia has a number of settings that you would traditionally configure by setting servlet parameters. With Spring Boot, you can use your `application.properties` file. To do so, -prefix the parameter with `management.jolokia.config.`, as shown in the following example: +prefix the parameter with `management.endpont.jolokia.config.`, as shown in the following +example: [source,properties,indent=0] ---- - management.jolokia.config.debug=true + management.endpoint.jolokia.config.debug=true ---- @@ -878,7 +885,7 @@ prefix the parameter with `management.jolokia.config.`, as shown in the followin [[production-ready-disabling-jolokia]] ==== Disabling Jolokia If you use Jolokia but do not want Spring Boot to configure it, set the -`management.jolokia.enabled` property to `false`, as follows: +`management.endpoint.jolokia.enabled` property to `false`, as follows: [source,properties,indent=0] ---- diff --git a/spring-boot-samples/spring-boot-sample-actuator-ui/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-actuator-ui/src/main/resources/application.properties index 3b6d1f0d01..4abde8d87b 100644 --- a/spring-boot-samples/spring-boot-sample-actuator-ui/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-actuator-ui/src/main/resources/application.properties @@ -2,4 +2,3 @@ spring.security.user.name=user spring.security.user.password=password management.health.diskspace.enabled=false management.endpoints.web.expose=* -management.jolokia.enabled=true