Add @EndpointServlet and migrate Jolokia

Add first class support for Servlet based endpoints and rework the
Jolokia endpoint to use it.

Fixes gh-10264
pull/11764/head
Phillip Webb 7 years ago
parent f8cdc01474
commit 017efda6ec

@ -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());
}
}

@ -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<Collection<OperationInvokerAdvisor>> invokerAdvisors,
ObjectProvider<Collection<EndpointFilter<ExposableServletEndpoint>>> filters) {
return new ServletEndpointDiscoverer(applicationContext,
webEndpointPathMapper,
filters.getIfAvailable(Collections::emptyList));
}
}
}

@ -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<EndpointServlet> {
private Map<String, String> initParameters;
public JolokiaEndpoint(Map<String, String> initParameters) {
this.initParameters = initParameters;
}
@Override
public EndpointServlet get() {
return new EndpointServlet(AgentServlet.class)
.withInitParameters(this.initParameters);
}
}

@ -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());
}
}

@ -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.
* <p>
* 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}.
* <p>
* Additional configuration parameters for Jolokia can be provided by specifying
* {@code management.jolokia.config.*} properties. See the
* <a href="http://jolokia.org">http://jolokia.org</a> 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<AgentServlet> jolokiaServlet() {
String path = this.managementServletContext.getServletPath()
+ this.properties.getPath();
String urlMapping = (path.endsWith("/") ? path + "*" : path + "/*");
ServletRegistrationBean<AgentServlet> registration = new ServletRegistrationBean<>(
new AgentServlet(), urlMapping);
registration.setInitParameters(this.properties.getConfig());
return registration;
}
}

@ -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<String, String> 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<String, String> getConfig() {
return this.config;
}

@ -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 @@
}
]
}

@ -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,\

@ -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();
}
}
}

@ -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));
}
}

@ -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<String> 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<String> 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<String> 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<String> 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 {
}

@ -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<ExposableServletEndpoint> 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<ExposableServletEndpoint> 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());
}
}
}

@ -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<AssertableWebApplicationContext> isDefinedOnPath(
String path) {
return (context) -> {
assertThat(context).hasSingleBean(ServletRegistrationBean.class);
ServletRegistrationBean<?> registrationBean = context
.getBean(ServletRegistrationBean.class);
assertThat(registrationBean.getUrlMappings()).containsExactly(path);
};
}
}

@ -291,6 +291,9 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
@SuppressWarnings("unchecked")
private boolean isFilterMatch(Class<?> filter, EndpointBean endpointBean) {
if (!isEndpointExposed(endpointBean.getBean())) {
return false;
}
if (filter == null) {
return true;
}

@ -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

@ -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<String, String> initParameters;
public EndpointServlet(Class<? extends Servlet> 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<String, String> 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<String, String> 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<String, String> mergedInitParameters = new LinkedHashMap<>(
this.initParameters);
mergedInitParameters.putAll(initParameters);
return new EndpointServlet(this.servlet, mergedInitParameters);
}
Servlet getServlet() {
return this.servlet;
}
Map<String, String> getInitParameters() {
return this.initParameters;
}
}

@ -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<Operation>, PathMappedEndpoint {
/**
* Return details of the servlet that should registered.
* @return the endpoint servlet
*/
EndpointServlet getEndpointServlet();
}

@ -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<ExposableServletEndpoint> servletEndpoints;
public ServletEndpointRegistrar(String basePath,
Collection<ExposableServletEndpoint> 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);
}
}

@ -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<EndpointFilter<ExposableControllerEndpoint>> 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;
}
}
}

@ -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
*/

@ -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<Operation>
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;
}
}

@ -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&lt;EndpointServlet&gt;}
* and return a valid {@link EndpointServlet}.
* <p>
* 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;
}

@ -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<ExposableServletEndpoint, Operation>
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<EndpointFilter<ExposableServletEndpoint>> 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<Operation> 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");
}
}

@ -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);
}
}

@ -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<ExposableServletEndpoint> {
}

@ -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<Servlet>) 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<String, String> 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 {
}
}
}

@ -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> 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 {
}
}
}

@ -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);
});
}

@ -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<ExposableServletEndpoint> 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<ExposableServletEndpoint> endpoints = discoverer.getEndpoints();
List<String> 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<ServletEndpointDiscoverer> 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<EndpointServlet> {
@Override
public EndpointServlet get() {
return new EndpointServlet(TestServlet.class);
}
}
@Endpoint(id = "test")
static class TestEndpoint {
}
@ServletEndpoint(id = "testservlet")
static class TestServletEndpointWithOperation implements Supplier<EndpointServlet> {
@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<String> {
@Override
public String get() {
return "error";
}
}
@ServletEndpoint(id = "testservlet")
static class TestServletEndpointSupplierOfNull implements Supplier<EndpointServlet> {
@Override
public EndpointServlet get() {
return null;
}
}
}

@ -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.

@ -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:
</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]
----

@ -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

Loading…
Cancel
Save