Update Actuator to use the new endpoint infrastructure

This commit migrates the Actuator onto the new endpoint infrastruture.
In addition to the existing support for accessing the endpoints via
JMX and HTTP using Spring MVC, support for access via HTTP using
Jersey and WebFlux has been added. This includes using a separate
management port where we now spin up an additional, appropriately
configured servlet or reactive web server to expose the management
context on a different HTTP port to the main application.

Closes gh-2921
Closes gh-5389
Closes gh-9796
This commit is contained in:
Andy Wilkinson 2017-07-12 09:08:43 +01:00
parent e92cb115e3
commit ee16332745
195 changed files with 7762 additions and 11963 deletions

View File

@ -153,6 +153,16 @@
<artifactId>flyway-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
@ -183,6 +193,11 @@
<artifactId>spring-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
@ -241,16 +256,6 @@
<artifactId>spring-integration-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
@ -261,11 +266,6 @@
<artifactId>spring-security-config</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>hal-browser</artifactId>
<optional>true</optional>
</dependency>
<!-- Annotation processing -->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -298,6 +298,10 @@
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
@ -324,6 +328,16 @@
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>

View File

@ -0,0 +1,42 @@
/*
* 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;
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 org.springframework.context.annotation.Conditional;
/**
* {@link Conditional} that matches based on the configuration of the management port.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnManagementPortCondition.class)
public @interface ConditionalOnManagementPort {
/**
* The {@link ManagementPortType} to match.
* @return the port type
*/
ManagementPortType value();
}

View File

@ -14,21 +14,17 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
package org.springframework.boot.actuate.autoconfigure;
import org.springframework.context.annotation.Configuration;
/**
* Callback for customizing the {@link EndpointHandlerMapping} at configuration time.
* Configurtaion class used to enable configuration of a child management context.
*
* @author Dave Syer
* @since 1.2.0
* @author Andy Wilkinson
*/
@FunctionalInterface
public interface EndpointHandlerMappingCustomizer {
/**
* Customize the specified {@link EndpointHandlerMapping}.
* @param mapping the {@link EndpointHandlerMapping} to customize
*/
void customize(EndpointHandlerMapping mapping);
@Configuration
@EnableManagementContext(ManagementContextType.CHILD)
class EnableChildManagementContextConfiguration {
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
package org.springframework.boot.actuate.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@ -22,31 +22,19 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.context.annotation.Import;
/**
* Specialized {@link RequestMapping} for {@link RequestMethod#GET GET} requests that
* produce {@code application/json} or
* {@code application/vnd.spring-boot.actuator.v1+json} responses.
* Enables the management context.
*
* @author Andy Wilkinson
*/
@Target(ElementType.METHOD)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET, produces = {
ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
@interface ActuatorGetMapping {
@Import(ManagementContextConfigurationImportSelector.class)
@interface EnableManagementContext {
/**
* Alias for {@link RequestMapping#value}.
* @return the value
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
ManagementContextType value();
}

View File

@ -0,0 +1,256 @@
/*
* 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;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.web.context.ConfigurableWebApplicationContext;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the management context. If the
* {@code management.port} is the same as the {@code server.port} the management context
* will be the same as the main application context. If the {@code management.port} is
* different to the {@code server.port} the management context will be a separate context
* that has the main application context as its parent.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
@Configuration
public class ManagementContextAutoConfiguration {
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
static class ServletManagementContextConfiguration {
@Bean
public ManagementServletContext managementServletContext(
final ManagementServerProperties properties) {
return () -> properties.getContextPath();
}
}
@Configuration
@ConditionalOnManagementPort(ManagementPortType.SAME)
static class SameManagementContextConfiguration
implements SmartInitializingSingleton {
private final Environment environment;
SameManagementContextConfiguration(Environment environment) {
this.environment = environment;
}
@Override
public void afterSingletonsInstantiated() {
veriifySslConfiguration();
verifyContextPathConfiguration();
if (this.environment instanceof ConfigurableEnvironment) {
addLocalManagementPortPropertyAlias(
(ConfigurableEnvironment) this.environment);
}
}
private void veriifySslConfiguration() {
if (this.environment.getProperty("management.ssl.enabled", Boolean.class,
false)) {
throw new IllegalStateException(
"Management-specific SSL cannot be configured as the management "
+ "server is not listening on a separate port");
}
}
private void verifyContextPathConfiguration() {
String contextPath = this.environment.getProperty("management.context-path");
if ("".equals(contextPath) || "/".equals(contextPath)) {
throw new IllegalStateException("A management context path of '"
+ contextPath + "' requires the management server to be "
+ "listening on a separate port");
}
}
/**
* Add an alias for 'local.management.port' that actually resolves using
* 'local.server.port'.
* @param environment the environment
*/
private void addLocalManagementPortPropertyAlias(
ConfigurableEnvironment environment) {
environment.getPropertySources()
.addLast(new PropertySource<Object>("Management Server") {
@Override
public Object getProperty(String name) {
if ("local.management.port".equals(name)) {
return environment.getProperty("local.server.port");
}
return null;
}
});
}
@EnableManagementContext(ManagementContextType.SAME)
static class EnableSameManagementContextConfiguration {
}
}
@Configuration
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
static class SeparateManagementContextConfiguration
implements SmartInitializingSingleton {
private final ApplicationContext applicationContext;
private final ManagementContextFactory managementContextFactory;
SeparateManagementContextConfiguration(ApplicationContext applicationContext,
ManagementContextFactory managementContextFactory) {
this.applicationContext = applicationContext;
this.managementContextFactory = managementContextFactory;
}
@Override
public void afterSingletonsInstantiated() {
ConfigurableApplicationContext managementContext = this.managementContextFactory
.createManagementContext(this.applicationContext,
EnableChildManagementContextConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
setNamespaceIfPossible(managementContext);
managementContext.setId(this.applicationContext.getId() + ":management");
setClassLoaderIfPossible(managementContext);
CloseManagementContextListener.addIfPossible(this.applicationContext,
managementContext);
managementContext.refresh();
}
private void setClassLoaderIfPossible(ConfigurableApplicationContext child) {
if (child instanceof DefaultResourceLoader) {
((AbstractApplicationContext) child)
.setClassLoader(this.applicationContext.getClassLoader());
}
}
private void setNamespaceIfPossible(ConfigurableApplicationContext child) {
if (child instanceof ConfigurableReactiveWebApplicationContext) {
((ConfigurableReactiveWebApplicationContext) child)
.setNamespace("management");
}
else if (child instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) child).setNamespace("management");
}
}
@ConditionalOnWebApplication(type = Type.SERVLET)
static class ServletChildContextConfiguration {
@Bean
public ServletWebManagementContextFactory servletWebChildContextFactory() {
return new ServletWebManagementContextFactory();
}
}
@ConditionalOnWebApplication(type = Type.REACTIVE)
static class ReactiveChildContextConfiguration {
@Bean
public ReactiveWebManagementContextFactory reactiveWebChildContextFactory() {
return new ReactiveWebManagementContextFactory();
}
}
}
/**
* {@link ApplicationListener} to propagate the {@link ContextClosedEvent} and
* {@link ApplicationFailedEvent} from a parent to a child.
*/
private static class CloseManagementContextListener
implements ApplicationListener<ApplicationEvent> {
private final ApplicationContext parentContext;
private final ConfigurableApplicationContext childContext;
CloseManagementContextListener(ApplicationContext parentContext,
ConfigurableApplicationContext childContext) {
this.parentContext = parentContext;
this.childContext = childContext;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent((ApplicationFailedEvent) event);
}
};
private void onContextClosedEvent(ContextClosedEvent event) {
propagateCloseIfNecessary(event.getApplicationContext());
}
private void onApplicationFailedEvent(ApplicationFailedEvent event) {
propagateCloseIfNecessary(event.getApplicationContext());
}
private void propagateCloseIfNecessary(ApplicationContext applicationContext) {
if (applicationContext == this.parentContext) {
this.childContext.close();
}
}
public static void addIfPossible(ApplicationContext parentContext,
ConfigurableApplicationContext childContext) {
if (parentContext instanceof ConfigurableApplicationContext) {
add((ConfigurableApplicationContext) parentContext, childContext);
}
}
private static void add(ConfigurableApplicationContext parentContext,
ConfigurableApplicationContext childContext) {
parentContext.addApplicationListener(
new CloseManagementContextListener(parentContext, childContext));
}
}
}

View File

@ -46,4 +46,12 @@ import org.springframework.core.annotation.Order;
@Configuration
public @interface ManagementContextConfiguration {
/**
* Specifies the type of management context that is required for this configuration to
* be applied.
* @return the required management context type
* @since 2.0.0
*/
ManagementContextType value() default ManagementContextType.ANY;
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint;
package org.springframework.boot.actuate.autoconfigure;
import java.io.IOException;
import java.util.ArrayList;
@ -22,7 +22,6 @@ import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
@ -44,19 +43,25 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
* @see ManagementContextConfiguration
*/
@Order(Ordered.LOWEST_PRECEDENCE)
class ManagementContextConfigurationsImportSelector
class ManagementContextConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public String[] selectImports(AnnotationMetadata metadata) {
ManagementContextType contextType = (ManagementContextType) metadata
.getAnnotationAttributes(EnableManagementContext.class.getName())
.get("value");
// Find all management context configuration classes, filtering duplicates
List<ManagementConfiguration> configurations = getConfigurations();
OrderComparator.sort(configurations);
List<String> names = new ArrayList<>();
for (ManagementConfiguration configuration : configurations) {
names.add(configuration.getClassName());
if (configuration.getContextType() == ManagementContextType.ANY
|| configuration.getContextType() == contextType) {
names.add(configuration.getClassName());
}
}
return names.toArray(new String[names.size()]);
}
@ -102,11 +107,23 @@ class ManagementContextConfigurationsImportSelector
private final int order;
private final ManagementContextType contextType;
ManagementConfiguration(MetadataReader metadataReader) {
AnnotationMetadata annotationMetadata = metadataReader
.getAnnotationMetadata();
this.order = readOrder(annotationMetadata);
this.className = metadataReader.getClassMetadata().getClassName();
this.contextType = readContextType(annotationMetadata);
}
private ManagementContextType readContextType(
AnnotationMetadata annotationMetadata) {
Map<String, Object> annotationAttributes = annotationMetadata
.getAnnotationAttributes(
ManagementContextConfiguration.class.getName());
return annotationAttributes == null ? ManagementContextType.ANY
: (ManagementContextType) annotationAttributes.get("value");
}
private int readOrder(AnnotationMetadata annotationMetadata) {
@ -126,6 +143,10 @@ class ManagementContextConfigurationsImportSelector
return this.order;
}
public ManagementContextType getContextType() {
return this.contextType;
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
/**
* A factory for creating a separate management context when the management web server is
* running on a different port to the main application.
*
* @author Andy Wilkinson
*/
interface ManagementContextFactory {
ConfigurableApplicationContext createManagementContext(ApplicationContext parent,
Class<?>... configClasses);
}

View File

@ -14,32 +14,31 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.web.bind.annotation.ResponseBody;
package org.springframework.boot.actuate.autoconfigure;
/**
* Adapter class to expose {@link Endpoint}s as {@link MvcEndpoint}s.
* Enumeration of management context types.
*
* @author Dave Syer
* @author Andy Wilkinson
* @since 2.0.0
*/
public class EndpointMvcAdapter extends AbstractEndpointMvcAdapter<Endpoint<?>> {
public enum ManagementContextType {
/**
* Create a new {@link EndpointMvcAdapter}.
* @param delegate the underlying {@link Endpoint} to adapt.
* The management context is the same as the main application context.
*/
public EndpointMvcAdapter(Endpoint<?> delegate) {
super(delegate);
}
SAME,
@Override
@ActuatorGetMapping
@ResponseBody
public Object invoke() {
return super.invoke();
}
/**
* The management context is a separate context that is a child of the main
* application context.
*/
CHILD,
/**
* The management context can be either the same as the main application context or a
* child of the main application context.
*/
ANY
}

View File

@ -0,0 +1,54 @@
/*
* 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;
import org.springframework.core.env.Environment;
public enum ManagementPortType {
/**
* The management port has been disabled.
*/
DISABLED,
/**
* The management port is the same as the server port.
*/
SAME,
/**
* The management port and server port are different.
*/
DIFFERENT;
static ManagementPortType get(Environment environment) {
Integer serverPort = getPortProperty(environment, "server.");
Integer managementPort = getPortProperty(environment, "management.");
if (managementPort != null && managementPort < 0) {
return DISABLED;
}
return ((managementPort == null)
|| (serverPort == null && managementPort.equals(8080))
|| (managementPort != 0 && managementPort.equals(serverPort)) ? SAME
: DIFFERENT);
}
private static Integer getPortProperty(Environment environment, String prefix) {
return environment.getProperty(prefix + "port", Integer.class);
}
}

View File

@ -0,0 +1,75 @@
/*
* 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;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.WebApplicationContext;
/**
* {@link SpringBootCondition} that matches when the management server is running on a
* different port.
*
* @author Andy Wilkinson
*/
class OnManagementPortCondition extends SpringBootCondition {
private static final String CLASS_NAME_WEB_APPLICATION_CONTEXT = "org.springframework.web.context.WebApplicationContext";
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Management Port");
if (!isWebApplicationContext(context)) {
return ConditionOutcome
.noMatch(message.because("non web application context"));
}
Map<String, Object> annotationAttributes = metadata
.getAnnotationAttributes(ConditionalOnManagementPort.class.getName());
ManagementPortType requiredType = (ManagementPortType) annotationAttributes
.get("value");
ManagementPortType actualType = ManagementPortType.get(context.getEnvironment());
if (actualType == requiredType) {
return ConditionOutcome.match(message.because(
"actual port type (" + actualType + ") matched required type"));
}
return ConditionOutcome.noMatch(message.because("actual port type (" + actualType
+ ") did not match required type (" + requiredType + ")"));
}
private boolean isWebApplicationContext(ConditionContext context) {
ResourceLoader resourceLoader = context.getResourceLoader();
if (resourceLoader instanceof ConfigurableReactiveWebApplicationContext) {
return true;
}
if (!ClassUtils.isPresent(CLASS_NAME_WEB_APPLICATION_CONTEXT,
context.getClassLoader())) {
return false;
}
return WebApplicationContext.class.isInstance(resourceLoader);
}
}

View File

@ -0,0 +1,85 @@
/*
* 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;
import java.lang.reflect.Modifier;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
/**
* A {@link ManagementContextFactory} for WebFlux-based web applications.
*
* @author Andy Wilkinson
*/
class ReactiveWebManagementContextFactory implements ManagementContextFactory {
@Override
public ConfigurableApplicationContext createManagementContext(
ApplicationContext parent, Class<?>... configClasses) {
ReactiveWebServerApplicationContext child = new ReactiveWebServerApplicationContext();
child.setParent(parent);
child.register(configClasses);
child.register(ReactiveWebServerAutoConfiguration.class);
registerReactiveWebServerFactory(parent, child);
return child;
}
private void registerReactiveWebServerFactory(ApplicationContext parent,
GenericReactiveWebApplicationContext childContext) {
try {
ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("ReactiveWebServerFactory",
new RootBeanDefinition(
determineReactiveWebServerFactoryClass(parent)));
}
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore and assume auto-configuration
}
}
private Class<?> determineReactiveWebServerFactoryClass(ApplicationContext parent)
throws NoSuchBeanDefinitionException {
Class<?> factoryClass = parent.getBean(ReactiveWebServerFactory.class).getClass();
if (cannotBeInstantiated(factoryClass)) {
throw new FatalBeanException("ReactiveWebServerFactory implementation "
+ factoryClass.getName() + " cannot be instantiated. "
+ "To allow a separate management port to be used, a top-level class "
+ "or static inner class should be used instead");
}
return factoryClass;
}
private boolean cannotBeInstantiated(Class<?> clazz) {
return clazz.isLocalClass()
|| (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers()))
|| clazz.isAnonymousClass();
}
}

View File

@ -0,0 +1,89 @@
/*
* 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;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
/**
* A {@link ManagementContextFactory} for servlet-based web applications.
*
* @author Andy Wilkinson
*/
class ServletWebManagementContextFactory implements ManagementContextFactory {
@Override
public ConfigurableApplicationContext createManagementContext(
ApplicationContext parent, Class<?>... configClasses) {
AnnotationConfigServletWebServerApplicationContext child = new AnnotationConfigServletWebServerApplicationContext();
child.setParent(parent);
List<Class<?>> combinedClasses = new ArrayList<Class<?>>(
Arrays.asList(configClasses));
combinedClasses.add(ServletWebServerFactoryAutoConfiguration.class);
child.register(combinedClasses.toArray(new Class<?>[combinedClasses.size()]));
registerServletWebServerFactory(parent, child);
return child;
}
private void registerServletWebServerFactory(ApplicationContext parent,
AnnotationConfigServletWebServerApplicationContext childContext) {
try {
ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("ServletWebServerFactory",
new RootBeanDefinition(
determineServletWebServerFactoryClass(parent)));
}
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore and assume auto-configuration
}
}
private Class<?> determineServletWebServerFactoryClass(ApplicationContext parent)
throws NoSuchBeanDefinitionException {
Class<?> factoryClass = parent.getBean(ServletWebServerFactory.class).getClass();
if (cannotBeInstantiated(factoryClass)) {
throw new FatalBeanException("ServletWebServerFactory implementation "
+ factoryClass.getName() + " cannot be instantiated. "
+ "To allow a separate management port to be used, a top-level class "
+ "or static inner class should be used instead");
}
return factoryClass;
}
private boolean cannotBeInstantiated(Class<?> clazz) {
return clazz.isLocalClass()
|| (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers()))
|| clazz.isAnonymousClass();
}
}

View File

@ -16,34 +16,18 @@
package org.springframework.boot.actuate.autoconfigure.cloudfoundry;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryEndpointHandlerMapping;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundrySecurityInterceptor;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundrySecurityService;
import org.springframework.boot.actuate.cloudfoundry.TokenValidator;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* {@link EnableAutoConfiguration Auto-configuration} to expose actuator endpoints for
@ -54,57 +38,10 @@ import org.springframework.web.servlet.HandlerInterceptor;
*/
@Configuration
@ConditionalOnProperty(prefix = "management.cloudfoundry", name = "enabled", matchIfMissing = true)
@ConditionalOnBean(MvcEndpoints.class)
@AutoConfigureAfter(EndpointWebMvcAutoConfiguration.class)
@AutoConfigureAfter(ServletEndpointAutoConfiguration.class)
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public class CloudFoundryActuatorAutoConfiguration {
@Bean
public CloudFoundryEndpointHandlerMapping cloudFoundryEndpointHandlerMapping(
MvcEndpoints mvcEndpoints, RestTemplateBuilder restTemplateBuilder,
Environment environment) {
Set<NamedMvcEndpoint> endpoints = new LinkedHashSet<>(
mvcEndpoints.getEndpoints(NamedMvcEndpoint.class));
HandlerInterceptor securityInterceptor = getSecurityInterceptor(
restTemplateBuilder, environment);
CorsConfiguration corsConfiguration = getCorsConfiguration();
CloudFoundryEndpointHandlerMapping mapping = new CloudFoundryEndpointHandlerMapping(
endpoints, corsConfiguration, securityInterceptor);
mapping.setPrefix("/cloudfoundryapplication");
return mapping;
}
private HandlerInterceptor getSecurityInterceptor(
RestTemplateBuilder restTemplateBuilder, Environment environment) {
CloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(
restTemplateBuilder, environment);
TokenValidator tokenValidator = new TokenValidator(cloudfoundrySecurityService);
HandlerInterceptor securityInterceptor = new CloudFoundrySecurityInterceptor(
tokenValidator, cloudfoundrySecurityService,
environment.getProperty("vcap.application.application_id"));
return securityInterceptor;
}
private CloudFoundrySecurityService getCloudFoundrySecurityService(
RestTemplateBuilder restTemplateBuilder, Environment environment) {
String cloudControllerUrl = environment.getProperty("vcap.application.cf_api");
boolean skipSslValidation = environment.getProperty(
"management.cloudfoundry.skip-ssl-validation", Boolean.class, false);
return cloudControllerUrl == null ? null
: new CloudFoundrySecurityService(restTemplateBuilder, cloudControllerUrl,
skipSslValidation);
}
private CorsConfiguration getCorsConfiguration() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin(CorsConfiguration.ALL);
corsConfiguration.setAllowedMethods(
Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name()));
corsConfiguration.setAllowedHeaders(
Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type"));
return corsConfiguration;
}
/**
* Nested configuration for ignored requests if Spring Security is present.
*/

View File

@ -0,0 +1,77 @@
/*
* 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.endpoint;
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 org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.EndpointType;
import org.springframework.context.annotation.Conditional;
/**
* {@link Conditional} that checks whether an endpoint is enabled or not. Matches
* according to the {@link Endpoint#enabledByDefault() enabledByDefault flag} and the
* specific {@link Endpoint#types() tech} that the endpoint may be restricted to.
* <p>
* If no specific {@code endpoints.<id>.*} or {@code endpoints.all.*} properties are
* defined, the condition matches the {@code enabledByDefault} value regardless of the
* specific {@link EndpointType}, if any. If any property are set, they are evaluated
* with a sensible order of precedence.
* <p>
* For instance if {@code endpoints.all.enabled} is {@code false} but
* {@code endpoints.<id>.enabled} is {@code true}, the condition will match.
* <p>
* This condition must be placed on a {@code @Bean} method producing an endpoint as its
* id and other attributes are inferred from the {@link Endpoint} annotation set on the
* return type of the factory method. Consider the following valid example:
*
* <pre class="code">
* &#064;Configuration
* public class MyAutoConfiguration {
*
* &#064;ConditionalOnEnabledEndpoint
* &#064;Bean
* public MyEndpoint myEndpoint() {
* ...
* }
*
* &#064;Endpoint(id = "my", enabledByDefault = false)
* static class MyEndpoint { ... }
*
* }</pre>
* <p>
*
* In the sample above the condition will be evaluated with the attributes specified on
* {@code MyEndpoint}. In particular, in the absence of any property in the environment,
* the condition will not match as this endpoint is disabled by default.
*
* @author Stephane Nicoll
* @since 2.0.0
* @see Endpoint
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Conditional(OnEnabledEndpointCondition.class)
public @interface ConditionalOnEnabledEndpoint {
}

View File

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.endpoint;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -26,11 +24,11 @@ import liquibase.integration.spring.SpringLiquibase;
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint;
import org.springframework.boot.actuate.endpoint.BeansEndpoint;
import org.springframework.boot.actuate.endpoint.ConfigurationPropertiesReportEndpoint;
import org.springframework.boot.actuate.endpoint.DumpEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.EndpointProperties;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.actuate.endpoint.FlywayEndpoint;
@ -42,6 +40,7 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.actuate.endpoint.ThreadDumpEndpoint;
import org.springframework.boot.actuate.endpoint.TraceEndpoint;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
@ -59,10 +58,12 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
/**
@ -78,114 +79,112 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
* @author Meang Akira Tanaka
* @author Ben Hale
* @since 2.0.0
* @author Andy Wilkinson
*/
@Configuration
@AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class })
@EnableConfigurationProperties(EndpointProperties.class)
public class EndpointAutoConfiguration {
private final HealthAggregator healthAggregator;
private final Map<String, HealthIndicator> healthIndicators;
private final List<InfoContributor> infoContributors;
private final Collection<PublicMetrics> publicMetrics;
private final TraceRepository traceRepository;
public EndpointAutoConfiguration(ObjectProvider<HealthAggregator> healthAggregator,
ObjectProvider<Map<String, HealthIndicator>> healthIndicators,
ObjectProvider<List<InfoContributor>> infoContributors,
ObjectProvider<Collection<PublicMetrics>> publicMetrics,
ObjectProvider<TraceRepository> traceRepository) {
this.healthAggregator = healthAggregator.getIfAvailable();
this.healthIndicators = healthIndicators.getIfAvailable();
this.infoContributors = infoContributors.getIfAvailable();
this.publicMetrics = publicMetrics.getIfAvailable();
this.traceRepository = traceRepository.getIfAvailable();
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public EnvironmentEndpoint environmentEndpoint(Environment environment) {
return new EnvironmentEndpoint(environment);
}
@Bean
@ConditionalOnMissingBean
public EnvironmentEndpoint environmentEndpoint() {
return new EnvironmentEndpoint();
}
@Bean
@ConditionalOnMissingBean
public HealthEndpoint healthEndpoint() {
@ConditionalOnEnabledEndpoint
public HealthEndpoint healthEndpoint(
ObjectProvider<HealthAggregator> healthAggregator,
ObjectProvider<Map<String, HealthIndicator>> healthIndicators) {
return new HealthEndpoint(
this.healthAggregator == null ? new OrderedHealthAggregator()
: this.healthAggregator,
this.healthIndicators == null
? Collections.<String, HealthIndicator>emptyMap()
: this.healthIndicators);
healthAggregator.getIfAvailable(() -> new OrderedHealthAggregator()),
healthIndicators.getIfAvailable(Collections::emptyMap));
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public BeansEndpoint beansEndpoint() {
return new BeansEndpoint();
}
@Bean
@ConditionalOnMissingBean
public InfoEndpoint infoEndpoint() throws Exception {
return new InfoEndpoint(this.infoContributors == null
? Collections.<InfoContributor>emptyList() : this.infoContributors);
@ConditionalOnEnabledEndpoint
public InfoEndpoint infoEndpoint(
ObjectProvider<List<InfoContributor>> infoContributors) {
return new InfoEndpoint(infoContributors.getIfAvailable(Collections::emptyList));
}
@Bean
@ConditionalOnBean(LoggingSystem.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) {
return new LoggersEndpoint(loggingSystem);
}
@Bean
@ConditionalOnMissingBean
public MetricsEndpoint metricsEndpoint() {
List<PublicMetrics> publicMetrics = new ArrayList<>();
if (this.publicMetrics != null) {
publicMetrics.addAll(this.publicMetrics);
}
publicMetrics.sort(AnnotationAwareOrderComparator.INSTANCE);
return new MetricsEndpoint(publicMetrics);
@ConditionalOnEnabledEndpoint
public MetricsEndpoint metricsEndpoint(
ObjectProvider<List<PublicMetrics>> publicMetrics) {
List<PublicMetrics> sortedPublicMetrics = publicMetrics
.getIfAvailable(Collections::emptyList);
Collections.sort(sortedPublicMetrics, AnnotationAwareOrderComparator.INSTANCE);
return new MetricsEndpoint(sortedPublicMetrics);
}
@Bean
@ConditionalOnMissingBean
public TraceEndpoint traceEndpoint() {
return new TraceEndpoint(this.traceRepository == null
? new InMemoryTraceRepository() : this.traceRepository);
@ConditionalOnEnabledEndpoint
public TraceEndpoint traceEndpoint(ObjectProvider<TraceRepository> traceRepository) {
return new TraceEndpoint(
traceRepository.getIfAvailable(() -> new InMemoryTraceRepository()));
}
@Bean
@ConditionalOnMissingBean
public DumpEndpoint dumpEndpoint() {
return new DumpEndpoint();
@ConditionalOnEnabledEndpoint
public ThreadDumpEndpoint dumpEndpoint() {
return new ThreadDumpEndpoint();
}
@Bean
@ConditionalOnBean(ConditionEvaluationReport.class)
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public AutoConfigurationReportEndpoint autoConfigurationReportEndpoint() {
return new AutoConfigurationReportEndpoint();
@ConditionalOnEnabledEndpoint
public AutoConfigurationReportEndpoint autoConfigurationReportEndpoint(
ConditionEvaluationReport conditionEvaluationReport) {
return new AutoConfigurationReportEndpoint(conditionEvaluationReport);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public ShutdownEndpoint shutdownEndpoint() {
return new ShutdownEndpoint();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint() {
return new ConfigurationPropertiesReportEndpoint();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(AuditEventRepository.class)
@ConditionalOnEnabledEndpoint
public AuditEventsEndpoint auditEventsEndpoint(
AuditEventRepository auditEventRepository) {
return new AuditEventsEndpoint(auditEventRepository);
}
@Configuration
@ConditionalOnBean(Flyway.class)
@ConditionalOnClass(Flyway.class)
@ -193,6 +192,7 @@ public class EndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public FlywayEndpoint flywayEndpoint(Map<String, Flyway> flyways) {
return new FlywayEndpoint(flyways);
}
@ -206,6 +206,7 @@ public class EndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public LiquibaseEndpoint liquibaseEndpoint(
Map<String, SpringLiquibase> liquibases) {
return new LiquibaseEndpoint(liquibases);
@ -219,6 +220,7 @@ public class EndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public RequestMappingEndpoint requestMappingEndpoint() {
RequestMappingEndpoint endpoint = new RequestMappingEndpoint();
return endpoint;

View File

@ -1,122 +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.endpoint;
import javax.management.MBeanServer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointMBeanExportAutoConfiguration.JmxEnabledCondition;
import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.jmx.AuditEventsJmxEndpoint;
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} to enable JMX export for
* {@link Endpoint}s.
*
* @author Christian Dupuis
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 2.0.0
*/
@Configuration
@Conditional(JmxEnabledCondition.class)
@AutoConfigureAfter({ EndpointAutoConfiguration.class, JmxAutoConfiguration.class })
@EnableConfigurationProperties(EndpointMBeanExportProperties.class)
public class EndpointMBeanExportAutoConfiguration {
private final EndpointMBeanExportProperties properties;
private final ObjectMapper objectMapper;
public EndpointMBeanExportAutoConfiguration(EndpointMBeanExportProperties properties,
ObjectProvider<ObjectMapper> objectMapper) {
this.properties = properties;
this.objectMapper = objectMapper.getIfAvailable();
}
@Bean
public EndpointMBeanExporter endpointMBeanExporter(MBeanServer server) {
EndpointMBeanExporter mbeanExporter = new EndpointMBeanExporter(
this.objectMapper);
String domain = this.properties.getDomain();
if (StringUtils.hasText(domain)) {
mbeanExporter.setDomain(domain);
}
mbeanExporter.setServer(server);
mbeanExporter.setEnsureUniqueRuntimeObjectNames(this.properties.isUniqueNames());
mbeanExporter.setObjectNameStaticProperties(this.properties.getStaticNames());
return mbeanExporter;
}
@Bean
@ConditionalOnMissingBean(MBeanServer.class)
public MBeanServer mbeanServer() {
return new JmxAutoConfiguration().mbeanServer();
}
@Bean
@ConditionalOnBean(AuditEventRepository.class)
@ConditionalOnEnabledEndpoint("auditevents")
public AuditEventsJmxEndpoint auditEventsEndpoint(
AuditEventRepository auditEventRepository) {
return new AuditEventsJmxEndpoint(this.objectMapper, auditEventRepository);
}
/**
* Condition to check that spring.jmx and endpoints.jmx are enabled.
*/
static class JmxEnabledCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
boolean jmxEnabled = context.getEnvironment()
.getProperty("spring.jmx.enabled", Boolean.class, true);
boolean jmxEndpointsEnabled = context.getEnvironment()
.getProperty("endpoints.jmx.enabled", Boolean.class, true);
if (jmxEnabled && jmxEndpointsEnabled) {
return ConditionOutcome.match(
ConditionMessage.forCondition("JMX Enabled").found("properties")
.items("spring.jmx.enabled", "endpoints.jmx.enabled"));
}
return ConditionOutcome.noMatch(ConditionMessage.forCondition("JMX Enabled")
.because("spring.jmx.enabled or endpoints.jmx.enabled is not set"));
}
}
}

View File

@ -1,88 +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.endpoint;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
/**
* Configuration properties for JMX endpoints.
*
* @author Christian Dupuis
* @since 2.0.0
*/
@ConfigurationProperties(prefix = "endpoints.jmx")
public class EndpointMBeanExportProperties {
/**
* JMX domain name. Initialized with the value of 'spring.jmx.default-domain' if set.
*/
@Value("${spring.jmx.default-domain:}")
private String domain;
/**
* Ensure that ObjectNames are modified in case of conflict.
*/
private boolean uniqueNames = false;
/**
* Enable the JMX endpoints.
*/
private boolean enabled = true;
/**
* Additional static properties to append to all ObjectNames of MBeans representing
* Endpoints.
*/
private Properties staticNames = new Properties();
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getDomain() {
return this.domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public boolean isUniqueNames() {
return this.uniqueNames;
}
public void setUniqueNames(boolean uniqueNames) {
this.uniqueNames = uniqueNames;
}
public Properties getStaticNames() {
return this.staticNames;
}
public void setStaticNames(String[] staticNames) {
this.staticNames = StringUtils.splitArrayElementsIntoProperties(staticNames, "=");
}
}

View File

@ -1,363 +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.endpoint;
import java.lang.reflect.Modifier;
import javax.servlet.Servlet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
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.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.filter.ApplicationContextHeaderFilter;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} to enable Spring MVC to handle
* {@link Endpoint} requests. If the {@link ManagementServerProperties} specifies a
* different port to {@link ServerProperties} a new child context is created, otherwise it
* is assumed that endpoint requests will be mapped and handled via an already registered
* {@link DispatcherServlet}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Christian Dupuis
* @author Andy Wilkinson
* @author Johannes Edmeier
* @author Eddú Meléndez
* @author Venil Noronha
* @author Madhura Bhave
* @since 2.0.0
*/
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ManagementServerProperties.class)
@AutoConfigureAfter({ EndpointAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class, WebMvcAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class, HypermediaAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class })
public class EndpointWebMvcAutoConfiguration
implements ApplicationContextAware, SmartInitializingSingleton {
private static final Log logger = LogFactory
.getLog(EndpointWebMvcAutoConfiguration.class);
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Bean
public ManagementContextResolver managementContextResolver() {
return new ManagementContextResolver(this.applicationContext);
}
@Bean
public ManagementServletContext managementServletContext(
final ManagementServerProperties properties) {
return properties::getContextPath;
}
@Override
public void afterSingletonsInstantiated() {
ManagementServerPort managementPort = ManagementServerPort.DIFFERENT;
Environment environment = this.applicationContext.getEnvironment();
if (this.applicationContext instanceof WebApplicationContext) {
managementPort = ManagementServerPort.get(environment);
}
if (managementPort == ManagementServerPort.DIFFERENT) {
if (this.applicationContext instanceof ServletWebServerApplicationContext
&& ((ServletWebServerApplicationContext) this.applicationContext)
.getWebServer() != null) {
createChildManagementContext();
}
else {
logger.warn("Could not start management web server on "
+ "different port (management endpoints are still available "
+ "through JMX)");
}
}
if (managementPort == ManagementServerPort.SAME) {
String contextPath = environment.getProperty("management.context-path");
if ("".equals(contextPath) || "/".equals(contextPath)) {
throw new IllegalStateException("A management context path of '"
+ contextPath + "' requires the management server to be "
+ "listening on a separate port");
}
if (environment.getProperty("management.ssl.enabled", Boolean.class, false)) {
throw new IllegalStateException(
"Management-specific SSL cannot be configured as the management "
+ "server is not listening on a separate port");
}
if (environment instanceof ConfigurableEnvironment) {
addLocalManagementPortPropertyAlias(
(ConfigurableEnvironment) environment);
}
}
}
private void createChildManagementContext() {
AnnotationConfigServletWebServerApplicationContext childContext = new AnnotationConfigServletWebServerApplicationContext();
childContext.setParent(this.applicationContext);
childContext.setNamespace("management");
childContext.setId(this.applicationContext.getId() + ":management");
childContext.setClassLoader(this.applicationContext.getClassLoader());
childContext.register(EndpointWebMvcChildContextConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class);
registerServletWebServerFactory(childContext);
CloseManagementContextListener.addIfPossible(this.applicationContext,
childContext);
childContext.refresh();
managementContextResolver().setApplicationContext(childContext);
}
private void registerServletWebServerFactory(
AnnotationConfigServletWebServerApplicationContext childContext) {
try {
ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("ServletWebServerFactory",
new RootBeanDefinition(determineServletWebServerFactoryClass()));
}
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore and assume auto-configuration
}
}
private Class<?> determineServletWebServerFactoryClass()
throws NoSuchBeanDefinitionException {
Class<?> factoryClass = this.applicationContext
.getBean(ServletWebServerFactory.class).getClass();
if (cannotBeInstantiated(factoryClass)) {
throw new FatalBeanException("ServletWebServerFactory implementation "
+ factoryClass.getName() + " cannot be instantiated. "
+ "To allow a separate management port to be used, a top-level class "
+ "or static inner class should be used instead");
}
return factoryClass;
}
private boolean cannotBeInstantiated(Class<?> clazz) {
return clazz.isLocalClass()
|| (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers()))
|| clazz.isAnonymousClass();
}
/**
* Add an alias for 'local.management.port' that actually resolves using
* 'local.server.port'.
* @param environment the environment
*/
private void addLocalManagementPortPropertyAlias(
final ConfigurableEnvironment environment) {
environment.getPropertySources()
.addLast(new PropertySource<Object>("Management Server") {
@Override
public Object getProperty(String name) {
if ("local.management.port".equals(name)) {
return environment.getProperty("local.server.port");
}
return null;
}
});
}
// Put Servlets and Filters in their own nested class so they don't force early
// instantiation of ManagementServerProperties.
@Configuration
@ConditionalOnProperty(prefix = "management", name = "add-application-context-header", havingValue = "true")
protected static class ApplicationContextFilterConfiguration {
@Bean
public ApplicationContextHeaderFilter applicationContextIdFilter(
ApplicationContext context) {
return new ApplicationContextHeaderFilter(context);
}
}
@Configuration
@Conditional(OnManagementMvcCondition.class)
@Import(ManagementContextConfigurationsImportSelector.class)
protected static class EndpointWebMvcConfiguration {
}
/**
* {@link ApplicationListener} to propagate the {@link ContextClosedEvent} and
* {@link ApplicationFailedEvent} from a parent to a child.
*/
private static class CloseManagementContextListener
implements ApplicationListener<ApplicationEvent> {
private final ApplicationContext parentContext;
private final ConfigurableApplicationContext childContext;
CloseManagementContextListener(ApplicationContext parentContext,
ConfigurableApplicationContext childContext) {
this.parentContext = parentContext;
this.childContext = childContext;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent((ApplicationFailedEvent) event);
}
};
private void onContextClosedEvent(ContextClosedEvent event) {
propagateCloseIfNecessary(event.getApplicationContext());
}
private void onApplicationFailedEvent(ApplicationFailedEvent event) {
propagateCloseIfNecessary(event.getApplicationContext());
}
private void propagateCloseIfNecessary(ApplicationContext applicationContext) {
if (applicationContext == this.parentContext) {
this.childContext.close();
}
}
public static void addIfPossible(ApplicationContext parentContext,
ConfigurableApplicationContext childContext) {
if (parentContext instanceof ConfigurableApplicationContext) {
add((ConfigurableApplicationContext) parentContext, childContext);
}
}
private static void add(ConfigurableApplicationContext parentContext,
ConfigurableApplicationContext childContext) {
parentContext.addApplicationListener(
new CloseManagementContextListener(parentContext, childContext));
}
}
private static class OnManagementMvcCondition extends SpringBootCondition
implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Management Server MVC");
if (!(context.getResourceLoader() instanceof WebApplicationContext)) {
return ConditionOutcome
.noMatch(message.because("non WebApplicationContext"));
}
ManagementServerPort port = ManagementServerPort
.get(context.getEnvironment());
if (port == ManagementServerPort.SAME) {
return ConditionOutcome.match(message.because("port is same"));
}
return ConditionOutcome.noMatch(message.because("port is not same"));
}
}
protected enum ManagementServerPort {
DISABLE, SAME, DIFFERENT;
public static ManagementServerPort get(Environment environment) {
Integer serverPort = getPortProperty(environment, "server.");
Integer managementPort = getPortProperty(environment, "management.");
if (managementPort != null && managementPort < 0) {
return DISABLE;
}
return ((managementPort == null)
|| (serverPort == null && managementPort.equals(8080))
|| (managementPort != 0 && managementPort.equals(serverPort)) ? SAME
: DIFFERENT);
}
private static Integer getPortProperty(Environment environment, String prefix) {
return environment.getProperty(prefix + "port", Integer.class);
}
}
}

View File

@ -1,241 +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.endpoint;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthMvcEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer;
import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.LogFileMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpointSecurityInterceptor;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsConfiguration;
/**
* Configuration to expose {@link Endpoint} instances over Spring MVC.
*
* @author Dave Syer
* @author Ben Hale
* @author Vedran Pavic
* @author Madhura Bhave
* @since 2.0.0
*/
@ManagementContextConfiguration
@EnableConfigurationProperties({ HealthMvcEndpointProperties.class,
EndpointCorsProperties.class })
public class EndpointWebMvcManagementContextConfiguration {
private final HealthMvcEndpointProperties healthMvcEndpointProperties;
private final ManagementServerProperties managementServerProperties;
private final EndpointCorsProperties corsProperties;
private final List<EndpointHandlerMappingCustomizer> mappingCustomizers;
public EndpointWebMvcManagementContextConfiguration(
HealthMvcEndpointProperties healthMvcEndpointProperties,
ManagementServerProperties managementServerProperties,
EndpointCorsProperties corsProperties,
ObjectProvider<List<EndpointHandlerMappingCustomizer>> mappingCustomizers) {
this.healthMvcEndpointProperties = healthMvcEndpointProperties;
this.managementServerProperties = managementServerProperties;
this.corsProperties = corsProperties;
List<EndpointHandlerMappingCustomizer> providedCustomizers = mappingCustomizers
.getIfAvailable();
this.mappingCustomizers = providedCustomizers == null
? Collections.<EndpointHandlerMappingCustomizer>emptyList()
: providedCustomizers;
}
@Bean
@ConditionalOnMissingBean
public EndpointHandlerMapping endpointHandlerMapping() {
Set<MvcEndpoint> endpoints = mvcEndpoints().getEndpoints();
CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties);
EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints,
corsConfiguration);
mapping.setPrefix(this.managementServerProperties.getContextPath());
MvcEndpointSecurityInterceptor securityInterceptor = new MvcEndpointSecurityInterceptor(
this.managementServerProperties.getSecurity().isEnabled(),
this.managementServerProperties.getSecurity().getRoles());
mapping.setSecurityInterceptor(securityInterceptor);
for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
customizer.customize(mapping);
}
return mapping;
}
private CorsConfiguration getCorsConfiguration(EndpointCorsProperties properties) {
if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
return null;
}
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(properties.getAllowedOrigins());
if (!CollectionUtils.isEmpty(properties.getAllowedHeaders())) {
configuration.setAllowedHeaders(properties.getAllowedHeaders());
}
if (!CollectionUtils.isEmpty(properties.getAllowedMethods())) {
configuration.setAllowedMethods(properties.getAllowedMethods());
}
if (!CollectionUtils.isEmpty(properties.getExposedHeaders())) {
configuration.setExposedHeaders(properties.getExposedHeaders());
}
if (properties.getMaxAge() != null) {
configuration.setMaxAge(properties.getMaxAge());
}
if (properties.getAllowCredentials() != null) {
configuration.setAllowCredentials(properties.getAllowCredentials());
}
return configuration;
}
@Bean
@ConditionalOnMissingBean
public MvcEndpoints mvcEndpoints() {
return new MvcEndpoints();
}
@Bean
@ConditionalOnBean(EnvironmentEndpoint.class)
@ConditionalOnEnabledEndpoint("env")
public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) {
return new EnvironmentMvcEndpoint(delegate);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint("heapdump")
public HeapdumpMvcEndpoint heapdumpMvcEndpoint() {
return new HeapdumpMvcEndpoint();
}
@Bean
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint("health")
public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate,
ManagementServerProperties managementServerProperties) {
HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate,
this.managementServerProperties.getSecurity().isEnabled(),
managementServerProperties.getSecurity().getRoles());
if (this.healthMvcEndpointProperties.getMapping() != null) {
healthMvcEndpoint
.addStatusMapping(this.healthMvcEndpointProperties.getMapping());
}
return healthMvcEndpoint;
}
@Bean
@ConditionalOnBean(LoggersEndpoint.class)
@ConditionalOnEnabledEndpoint("loggers")
public LoggersMvcEndpoint loggersMvcEndpoint(LoggersEndpoint delegate) {
return new LoggersMvcEndpoint(delegate);
}
@Bean
@ConditionalOnBean(MetricsEndpoint.class)
@ConditionalOnEnabledEndpoint("metrics")
public MetricsMvcEndpoint metricsMvcEndpoint(MetricsEndpoint delegate) {
return new MetricsMvcEndpoint(delegate);
}
@Bean
@ConditionalOnEnabledEndpoint("logfile")
@Conditional(LogFileCondition.class)
public LogFileMvcEndpoint logfileMvcEndpoint() {
return new LogFileMvcEndpoint();
}
@Bean
@ConditionalOnBean(ShutdownEndpoint.class)
@ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false)
public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) {
return new ShutdownMvcEndpoint(delegate);
}
@Bean
@ConditionalOnBean(AuditEventRepository.class)
@ConditionalOnEnabledEndpoint("auditevents")
public AuditEventsMvcEndpoint auditEventMvcEndpoint(
AuditEventRepository auditEventRepository) {
return new AuditEventsMvcEndpoint(auditEventRepository);
}
private static class LogFileCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String config = environment.resolvePlaceholders("${logging.file:}");
ConditionMessage.Builder message = ConditionMessage.forCondition("Log File");
if (StringUtils.hasText(config)) {
return ConditionOutcome
.match(message.found("logging.file").items(config));
}
config = environment.resolvePlaceholders("${logging.path:}");
if (StringUtils.hasText(config)) {
return ConditionOutcome
.match(message.found("logging.path").items(config));
}
config = environment.getProperty("endpoints.logfile.external-file");
if (StringUtils.hasText(config)) {
return ConditionOutcome.match(
message.found("endpoints.logfile.external-file").items(config));
}
return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll());
}
}
}

View File

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.endpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.context.ApplicationContext;
/**
@ -34,23 +33,6 @@ public class ManagementContextResolver {
this.applicationContext = applicationContext;
}
void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Return all {@link MvcEndpoints} from the management context.
* @return {@link MvcEndpoints} from the management context
*/
public MvcEndpoints getMvcEndpoints() {
try {
return getApplicationContext().getBean(MvcEndpoints.class);
}
catch (Exception ex) {
return null;
}
}
/**
* Return the management {@link ApplicationContext}.
* @return the management {@link ApplicationContext}

View File

@ -0,0 +1,131 @@
/*
* 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.endpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.support.EndpointEnablement;
import org.springframework.boot.actuate.autoconfigure.endpoint.support.EndpointEnablementProvider;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.EndpointType;
import org.springframework.boot.endpoint.jmx.JmxEndpointExtension;
import org.springframework.boot.endpoint.web.WebEndpointExtension;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* A condition that checks if an endpoint is enabled.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
*/
class OnEnabledEndpointCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
EndpointAttributes endpoint = getEndpointAttributes(context, metadata);
if (!StringUtils.hasText(endpoint.id)) {
throw new IllegalStateException("Endpoint id could not be determined");
}
EndpointEnablementProvider enablementProvider = new EndpointEnablementProvider(
context.getEnvironment());
EndpointEnablement endpointEnablement = enablementProvider.getEndpointEnablement(
endpoint.id, endpoint.enabled, endpoint.endpointType);
return new ConditionOutcome(endpointEnablement.isEnabled(),
ConditionMessage.forCondition(ConditionalOnEnabledEndpoint.class)
.because(endpointEnablement.getReason()));
}
private EndpointAttributes getEndpointAttributes(ConditionContext context,
AnnotatedTypeMetadata metadata) {
if (metadata instanceof MethodMetadata
&& metadata.isAnnotated(Bean.class.getName())) {
MethodMetadata methodMetadata = (MethodMetadata) metadata;
try {
// We should be safe to load at this point since we are in the
// REGISTER_BEAN phase
Class<?> returnType = ClassUtils.forName(
methodMetadata.getReturnTypeName(), context.getClassLoader());
return extractEndpointAttributes(returnType);
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to extract endpoint id for "
+ methodMetadata.getDeclaringClassName() + "."
+ methodMetadata.getMethodName(), ex);
}
}
throw new IllegalStateException(
"OnEnabledEndpointCondition may only be used on @Bean methods");
}
protected EndpointAttributes extractEndpointAttributes(Class<?> type) {
EndpointAttributes attributes = extractEndpointAttributesFromEndpoint(type);
if (attributes != null) {
return attributes;
}
JmxEndpointExtension jmxExtension = AnnotationUtils.findAnnotation(type,
JmxEndpointExtension.class);
if (jmxExtension != null) {
return extractEndpointAttributes(jmxExtension.endpoint());
}
WebEndpointExtension webExtension = AnnotationUtils.findAnnotation(type,
WebEndpointExtension.class);
if (webExtension != null) {
return extractEndpointAttributes(webExtension.endpoint());
}
throw new IllegalStateException(
"OnEnabledEndpointCondition may only be used on @Bean methods that return"
+ " @Endpoint, @JmxEndpointExtension, or @WebEndpointExtension");
}
private EndpointAttributes extractEndpointAttributesFromEndpoint(
Class<?> endpointClass) {
Endpoint endpoint = AnnotationUtils.findAnnotation(endpointClass, Endpoint.class);
if (endpoint == null) {
return null;
}
// If both types are set, all techs are exposed
EndpointType endpointType = (endpoint.types().length == 1 ? endpoint.types()[0]
: null);
return new EndpointAttributes(endpoint.id(), endpoint.enabledByDefault(),
endpointType);
}
private static class EndpointAttributes {
private final String id;
private final boolean enabled;
private final EndpointType endpointType;
EndpointAttributes(String id, boolean enabled, EndpointType endpointType) {
this.id = id;
this.enabled = enabled;
this.endpointType = endpointType;
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;
import org.springframework.http.MediaType;
@ -23,7 +23,7 @@ import org.springframework.http.MediaType;
*
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 1.5.0
* @since 2.0.0
*/
public final class ActuatorMediaTypes {

View File

@ -0,0 +1,49 @@
/*
* 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.endpoint.infrastructure;
import java.util.function.Function;
import org.springframework.boot.endpoint.CachingConfiguration;
import org.springframework.core.env.Environment;
/**
* A {@link CachingConfiguration} factory that use the {@link Environment} to extract
* the caching settings of each endpoint.
*
* @author Stephane Nicoll
*/
class CachingConfigurationFactory
implements Function<String, CachingConfiguration> {
private final Environment environment;
/**
* Create a new instance with the {@link Environment} to use.
* @param environment the environment
*/
CachingConfigurationFactory(Environment environment) {
this.environment = environment;
}
@Override
public CachingConfiguration apply(String endpointId) {
String key = String.format("endpoints.%s.cache.time-to-live", endpointId);
Long ttl = this.environment.getProperty(key, Long.class, 0L);
return new CachingConfiguration(ttl);
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.endpoint.infrastructure;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.springframework.boot.endpoint.jmx.EndpointMBean;
import org.springframework.boot.endpoint.jmx.EndpointObjectNameFactory;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.StringUtils;
/**
* A {@link EndpointObjectNameFactory} that generates standard {@link ObjectName} for
* Actuator's endpoints.
*
* @author Stephane Nicoll
*/
class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
private String domain = "org.springframework.boot";
@Override
public ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException {
StringBuilder builder = new StringBuilder();
builder.append(this.domain);
builder.append(":type=Endpoint");
builder.append(",name=" + StringUtils.capitalize(mBean.getEndpointId()));
return ObjectNameManager.getInstance(builder.toString());
}
}

View File

@ -0,0 +1,141 @@
/*
* 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.endpoint.infrastructure;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.management.MBeanServer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper;
import org.springframework.boot.endpoint.EndpointType;
import org.springframework.boot.endpoint.OperationParameterMapper;
import org.springframework.boot.endpoint.jmx.EndpointMBeanRegistrar;
import org.springframework.boot.endpoint.jmx.JmxAnnotationEndpointDiscoverer;
import org.springframework.boot.endpoint.jmx.JmxEndpointOperation;
import org.springframework.boot.endpoint.web.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.endpoint.web.WebEndpointOperation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the endpoint infrastructure used
* by the Actuator.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @since 2.0.0
*/
@Configuration
@AutoConfigureAfter(JmxAutoConfiguration.class)
public class EndpointInfrastructureAutoConfiguration {
private final ApplicationContext applicationContext;
public EndpointInfrastructureAutoConfiguration(
ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
public OperationParameterMapper operationParameterMapper() {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(String.class, Date.class, (string) -> {
if (StringUtils.hasLength(string)) {
OffsetDateTime offsetDateTime = OffsetDateTime.parse(string,
DateTimeFormatter.ISO_OFFSET_DATE_TIME);
return new Date(offsetDateTime.toEpochSecond() * 1000);
}
return null;
});
return new ConversionServiceOperationParameterMapper(conversionService);
}
@Bean
public CachingConfigurationFactory cacheConfigurationFactory() {
return new CachingConfigurationFactory(this.applicationContext.getEnvironment());
}
@Bean
public JmxAnnotationEndpointDiscoverer jmxEndpointDiscoverer(
OperationParameterMapper operationParameterMapper,
CachingConfigurationFactory cachingConfigurationFactory) {
return new JmxAnnotationEndpointDiscoverer(this.applicationContext,
operationParameterMapper, cachingConfigurationFactory);
}
@ConditionalOnSingleCandidate(MBeanServer.class)
@Bean
public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer,
JmxAnnotationEndpointDiscoverer endpointDiscoverer,
ObjectProvider<ObjectMapper> objectMapper) {
EndpointProvider<JmxEndpointOperation> endpointProvider = new EndpointProvider<>(
this.applicationContext.getEnvironment(), endpointDiscoverer,
EndpointType.JMX);
EndpointMBeanRegistrar endpointMBeanRegistrar = new EndpointMBeanRegistrar(
mBeanServer, new DefaultEndpointObjectNameFactory());
return new JmxEndpointExporter(endpointProvider, endpointMBeanRegistrar,
objectMapper.getIfAvailable(ObjectMapper::new));
}
@ConditionalOnWebApplication
static class WebInfrastructureConfiguration {
private final ApplicationContext applicationContext;
WebInfrastructureConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
public EndpointProvider<WebEndpointOperation> webEndpointProvider(
OperationParameterMapper operationParameterMapper,
CachingConfigurationFactory cachingConfigurationFactory) {
return new EndpointProvider<>(this.applicationContext.getEnvironment(),
webEndpointDiscoverer(operationParameterMapper,
cachingConfigurationFactory),
EndpointType.WEB);
}
private WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
OperationParameterMapper operationParameterMapper,
CachingConfigurationFactory cachingConfigurationFactory) {
List<String> mediaTypes = Arrays.asList(
ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE,
"application/json");
return new WebAnnotationEndpointDiscoverer(this.applicationContext,
operationParameterMapper, cachingConfigurationFactory, mediaTypes,
mediaTypes);
}
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.endpoint.infrastructure;
import java.util.Collection;
import java.util.stream.Collectors;
import org.springframework.boot.actuate.autoconfigure.endpoint.support.EndpointEnablementProvider;
import org.springframework.boot.endpoint.EndpointDiscoverer;
import org.springframework.boot.endpoint.EndpointInfo;
import org.springframework.boot.endpoint.EndpointOperation;
import org.springframework.boot.endpoint.EndpointType;
import org.springframework.core.env.Environment;
/**
* Provides the endpoints that are enabled according to an {@link EndpointDiscoverer} and
* the current {@link Environment}.
*
* @param <T> the endpoint operation type
* @author Stephane Nicoll
* @since 2.0.0
*/
public final class EndpointProvider<T extends EndpointOperation> {
private final EndpointDiscoverer<T> discoverer;
private final EndpointEnablementProvider endpointEnablementProvider;
private final EndpointType endpointType;
/**
* Creates a new instance.
* @param environment the environment to use to check the endpoints that are enabled
* @param discoverer the discoverer to get the initial set of endpoints
* @param endpointType the type of endpoint to handle
*/
public EndpointProvider(Environment environment, EndpointDiscoverer<T> discoverer,
EndpointType endpointType) {
this.discoverer = discoverer;
this.endpointEnablementProvider = new EndpointEnablementProvider(environment);
this.endpointType = endpointType;
}
public Collection<EndpointInfo<T>> getEndpoints() {
return this.discoverer.discoverEndpoints().stream().filter(this::isEnabled)
.collect(Collectors.toList());
}
private boolean isEnabled(EndpointInfo<?> endpoint) {
return this.endpointEnablementProvider.getEndpointEnablement(endpoint.getId(),
endpoint.isEnabledByDefault(), this.endpointType).isEnabled();
}
}

View File

@ -0,0 +1,134 @@
/*
* 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.endpoint.infrastructure;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.jmx.EndpointMBean;
import org.springframework.boot.endpoint.jmx.EndpointMBeanRegistrar;
import org.springframework.boot.endpoint.jmx.JmxEndpointMBeanFactory;
import org.springframework.boot.endpoint.jmx.JmxEndpointOperation;
import org.springframework.boot.endpoint.jmx.JmxOperationResponseMapper;
/**
* Exports all available {@link Endpoint} to a configurable {@link MBeanServer}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
class JmxEndpointExporter implements InitializingBean, DisposableBean {
private final EndpointProvider<JmxEndpointOperation> endpointProvider;
private final EndpointMBeanRegistrar endpointMBeanRegistrar;
private final JmxEndpointMBeanFactory mBeanFactory;
private Collection<ObjectName> registeredObjectNames;
JmxEndpointExporter(EndpointProvider<JmxEndpointOperation> endpointProvider,
EndpointMBeanRegistrar endpointMBeanRegistrar,
ObjectMapper objectMapper) {
this.endpointProvider = endpointProvider;
this.endpointMBeanRegistrar = endpointMBeanRegistrar;
DataConverter dataConverter = new DataConverter(objectMapper);
this.mBeanFactory = new JmxEndpointMBeanFactory(dataConverter);
}
@Override
public void afterPropertiesSet() {
this.registeredObjectNames = registerEndpointMBeans();
}
@Override
public void destroy() throws Exception {
unregisterEndpointMBeans(this.registeredObjectNames);
}
private Collection<ObjectName> registerEndpointMBeans() {
List<ObjectName> objectNames = new ArrayList<>();
Collection<EndpointMBean> mBeans = this.mBeanFactory.createMBeans(
this.endpointProvider.getEndpoints());
for (EndpointMBean mBean : mBeans) {
objectNames.add(this.endpointMBeanRegistrar.registerEndpointMBean(mBean));
}
return objectNames;
}
private void unregisterEndpointMBeans(Collection<ObjectName> objectNames) {
objectNames.forEach(this.endpointMBeanRegistrar::unregisterEndpointMbean);
}
static class DataConverter implements JmxOperationResponseMapper {
private final ObjectMapper objectMapper;
private final JavaType listObject;
private final JavaType mapStringObject;
DataConverter(ObjectMapper objectMapper) {
this.objectMapper = (objectMapper == null ? new ObjectMapper()
: objectMapper);
this.listObject = this.objectMapper.getTypeFactory()
.constructParametricType(List.class, Object.class);
this.mapStringObject = this.objectMapper.getTypeFactory()
.constructParametricType(Map.class, String.class, Object.class);
}
@Override
public Object mapResponse(Object response) {
if (response == null) {
return null;
}
if (response instanceof String) {
return response;
}
if (response.getClass().isArray() || response instanceof Collection) {
return this.objectMapper.convertValue(response, this.listObject);
}
return this.objectMapper.convertValue(response, this.mapStringObject);
}
@Override
public Class<?> mapResponseType(Class<?> responseType) {
if (responseType.equals(String.class)) {
return String.class;
}
if (responseType.isArray() || Collection.class.isAssignableFrom(responseType)) {
return List.class;
}
return Map.class;
}
}
}

View File

@ -0,0 +1,89 @@
/*
* 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.endpoint.infrastructure;
import java.util.Collections;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.Ordered;
/**
* {@link WebServerFactoryCustomizer} that customizes the {@link WebServerFactory} used to
* create the management context's web server.
*
* @param <T> the type of web server factory to customize
* @author Andy Wilkinson
*/
abstract class ManagementWebServerFactoryCustomizer<T extends ConfigurableWebServerFactory>
implements WebServerFactoryCustomizer<T>, Ordered {
private final ListableBeanFactory beanFactory;
private final Class<? extends WebServerFactoryCustomizer<T>> customizerClass;
protected ManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory,
Class<? extends WebServerFactoryCustomizer<T>> customizerClass) {
this.beanFactory = beanFactory;
this.customizerClass = customizerClass;
}
@Override
public int getOrder() {
return 0;
}
@Override
public final void customize(T webServerFactory) {
ManagementServerProperties managementServerProperties = BeanFactoryUtils
.beanOfTypeIncludingAncestors(this.beanFactory,
ManagementServerProperties.class);
ServerProperties serverProperties = BeanFactoryUtils
.beanOfTypeIncludingAncestors(this.beanFactory, ServerProperties.class);
WebServerFactoryCustomizer<T> webServerFactoryCustomizer = BeanFactoryUtils
.beanOfTypeIncludingAncestors(this.beanFactory, this.customizerClass);
// Customize as per the parent context first (so e.g. the access logs go to
// the same place)
webServerFactoryCustomizer.customize(webServerFactory);
// Then reset the error pages
webServerFactory.setErrorPages(Collections.<ErrorPage>emptySet());
// and add the management-specific bits
customize(webServerFactory, managementServerProperties, serverProperties);
}
protected void customize(T webServerFactory,
ManagementServerProperties managementServerProperties,
ServerProperties serverProperties) {
webServerFactory.setPort(managementServerProperties.getPort());
Ssl ssl = managementServerProperties.getSsl();
if (ssl != null) {
webServerFactory.setSsl(ssl);
}
webServerFactory.setServerHeader(serverProperties.getServerHeader());
webServerFactory.setAddress(managementServerProperties.getAddress());
webServerFactory
.addErrorPages(new ErrorPage(serverProperties.getError().getPath()));
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.endpoint.infrastructure;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.ManagementContextType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.reactive.DefaultReactiveWebServerCustomizer;
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
/**
* Configuration for reactive web endpoint infrastructure when a separate management
* context with a web server running on a different port is required.
*
* @author Andy Wilkinson
*/
@EnableWebFlux
@ManagementContextConfiguration(ManagementContextType.CHILD)
@ConditionalOnWebApplication(type = Type.REACTIVE)
class ReactiveEndpointChildManagementContextConfiguration {
@Bean
public HttpHandler httpHandler(ApplicationContext applicationContext) {
return WebHttpHandlerBuilder.applicationContext(applicationContext).build();
}
@Bean
public ManagementReactiveWebServerFactoryCustomizer webServerFactoryCustomizer(
ListableBeanFactory beanFactory) {
return new ManagementReactiveWebServerFactoryCustomizer(beanFactory);
}
static class ManagementReactiveWebServerFactoryCustomizer extends
ManagementWebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {
ManagementReactiveWebServerFactoryCustomizer(ListableBeanFactory beanFactory) {
super(beanFactory, DefaultReactiveWebServerCustomizer.class);
}
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.endpoint.infrastructure;
import javax.servlet.Servlet;
import org.springframework.boot.actuate.autoconfigure.endpoint.ManagementContextResolver;
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.filter.ApplicationContextHeaderFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Servlet-specific endpoint
* concerns.
*
* @author Dave Syer
* @author Phillip Webb
* @author Christian Dupuis
* @author Andy Wilkinson
* @author Johannes Edmeier
* @author Eddú Meléndez
* @author Venil Noronha
* @author Madhura Bhave
*/
@Configuration
@ConditionalOnClass(Servlet.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ManagementServerProperties.class)
@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class, WebMvcAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class, HypermediaAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
EndpointInfrastructureAutoConfiguration.class })
public class ServletEndpointAutoConfiguration {
@Bean
public ManagementContextResolver managementContextResolver(
ApplicationContext applicationContext) {
return new ManagementContextResolver(applicationContext);
}
// Put Servlets and Filters in their own nested class so they don't force early
// instantiation of ManagementServerProperties.
@Configuration
@ConditionalOnProperty(prefix = "management", name = "add-application-context-header", havingValue = "true")
protected static class ApplicationContextFilterConfiguration {
@Bean
public ApplicationContextHeaderFilter applicationContextIdFilter(
ApplicationContext context) {
return new ApplicationContextHeaderFilter(context);
}
}
}

View File

@ -14,10 +14,9 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint;
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.Filter;
@ -26,38 +25,37 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Valve;
import org.apache.catalina.valves.AccessLogValve;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.ManagementContextType;
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.ManagementErrorEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.hateoas.HypermediaHttpMessageConverterConfiguration;
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DefaultServletWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerAdapter;
@ -68,50 +66,23 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* Configuration triggered from {@link EndpointWebMvcAutoConfiguration} when a new
* {@link WebServer} running on a different port is required.
* Configuration for Servlet web endpoint infrastructure when a separate management
* context with a web server running on a different port is required.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Eddú Meléndez
* @see EndpointWebMvcAutoConfiguration
* @since 2.0.0
*/
@Configuration
@EnableWebMvc
@Import(ManagementContextConfigurationsImportSelector.class)
public class EndpointWebMvcChildContextConfiguration {
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// Ensure the parent configuration does not leak down to us
dispatcherServlet.setDetectAllHandlerAdapters(false);
dispatcherServlet.setDetectAllHandlerExceptionResolvers(false);
dispatcherServlet.setDetectAllHandlerMappings(false);
dispatcherServlet.setDetectAllViewResolvers(false);
return dispatcherServlet;
}
@Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME)
public CompositeHandlerMapping compositeHandlerMapping() {
return new CompositeHandlerMapping();
}
@Bean(name = DispatcherServlet.HANDLER_ADAPTER_BEAN_NAME)
public CompositeHandlerAdapter compositeHandlerAdapter() {
return new CompositeHandlerAdapter();
}
@Bean(name = DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME)
public CompositeHandlerExceptionResolver compositeHandlerExceptionResolver() {
return new CompositeHandlerExceptionResolver();
}
@ManagementContextConfiguration(ManagementContextType.CHILD)
@ConditionalOnWebApplication(type = Type.SERVLET)
class ServletEndpointChildManagementContextConfiguration {
@Bean
public ServerFactoryCustomization serverCustomization() {
return new ServerFactoryCustomization();
public ManagementServletWebServerFactoryCustomization serverCustomization(
ListableBeanFactory beanFactory) {
return new ManagementServletWebServerFactoryCustomization(beanFactory);
}
@Bean
@ -125,28 +96,75 @@ public class EndpointWebMvcChildContextConfiguration {
return new TomcatAccessLogCustomizer();
}
/*
* The error controller is present but not mapped as an endpoint in this context
* because of the DispatcherServlet having had its HandlerMapping explicitly disabled.
* So we expose the same feature but only for machine endpoints.
*/
@Bean
@ConditionalOnBean(ErrorAttributes.class)
public ManagementErrorEndpoint errorEndpoint(ErrorAttributes errorAttributes) {
return new ManagementErrorEndpoint(errorAttributes);
@EnableWebMvc
@ConditionalOnClass(DispatcherServlet.class)
static class MvcEndpointChildContextConfiguration {
/*
* The error controller is present but not mapped as an endpoint in this context
* because of the DispatcherServlet having had its HandlerMapping explicitly
* disabled. So we expose the same feature but only for machine endpoints.
*/
@Bean
@ConditionalOnBean(ErrorAttributes.class)
public ManagementErrorEndpoint errorEndpoint(ErrorAttributes errorAttributes) {
return new ManagementErrorEndpoint(errorAttributes);
}
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// Ensure the parent configuration does not leak down to us
dispatcherServlet.setDetectAllHandlerAdapters(false);
dispatcherServlet.setDetectAllHandlerExceptionResolvers(false);
dispatcherServlet.setDetectAllHandlerMappings(false);
dispatcherServlet.setDetectAllViewResolvers(false);
return dispatcherServlet;
}
@Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME)
public CompositeHandlerMapping compositeHandlerMapping() {
return new CompositeHandlerMapping();
}
@Bean(name = DispatcherServlet.HANDLER_ADAPTER_BEAN_NAME)
public CompositeHandlerAdapter compositeHandlerAdapter() {
return new CompositeHandlerAdapter();
}
@Bean(name = DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME)
public CompositeHandlerExceptionResolver compositeHandlerExceptionResolver() {
return new CompositeHandlerExceptionResolver();
}
}
/**
* Configuration to add {@link HandlerMapping} for {@link MvcEndpoint}s.
*/
@Configuration
protected static class EndpointHandlerMappingConfiguration {
@ConditionalOnClass(ResourceConfig.class)
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
static class JerseyEndpointChildContextConfiguration {
@Autowired
public void handlerMapping(MvcEndpoints endpoints,
ListableBeanFactory beanFactory, EndpointHandlerMapping mapping) {
// In a child context we definitely want to see the parent endpoints
mapping.setDetectHandlerMethodsInAncestorContexts(true);
private final List<ResourceConfigCustomizer> resourceConfigCustomizers;
JerseyEndpointChildContextConfiguration(
List<ResourceConfigCustomizer> resourceConfigCustomizers) {
this.resourceConfigCustomizers = resourceConfigCustomizers;
}
@Bean
public ServletRegistrationBean<ServletContainer> jerseyServletRegistration() {
ServletRegistrationBean<ServletContainer> registration = new ServletRegistrationBean<>(
new ServletContainer(endpointResourceConfig()), "/*");
return registration;
}
@Bean
public ResourceConfig endpointResourceConfig() {
ResourceConfig resourceConfig = new ResourceConfig();
for (ResourceConfigCustomizer customizer : this.resourceConfigCustomizers) {
customizer.customize(resourceConfig);
}
return resourceConfig;
}
}
@ -154,7 +172,7 @@ public class EndpointWebMvcChildContextConfiguration {
@Configuration
@ConditionalOnClass({ EnableWebSecurity.class, Filter.class })
@ConditionalOnBean(name = "springSecurityFilterChain", search = SearchStrategy.ANCESTORS)
public static class EndpointWebMvcChildContextSecurityConfiguration {
static class EndpointWebMvcChildContextSecurityConfiguration {
@Bean
public Filter springSecurityFilterChain(HierarchicalBeanFactory beanFactory) {
@ -164,60 +182,20 @@ public class EndpointWebMvcChildContextConfiguration {
}
@Configuration
@ConditionalOnClass({ LinkDiscoverer.class })
@Import(HypermediaHttpMessageConverterConfiguration.class)
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
static class HypermediaConfiguration {
static class ManagementServletWebServerFactoryCustomization extends
ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
}
static class ServerFactoryCustomization implements
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
@Autowired
private ListableBeanFactory beanFactory;
// This needs to be lazily initialized because web server customizer
// instances get their callback very early in the context lifecycle.
private ManagementServerProperties managementServerProperties;
private ServerProperties server;
private DefaultServletWebServerFactoryCustomizer serverCustomizer;
@Override
public int getOrder() {
return 0;
ManagementServletWebServerFactoryCustomization(ListableBeanFactory beanFactory) {
super(beanFactory, DefaultServletWebServerFactoryCustomizer.class);
}
@Override
public void customize(ConfigurableServletWebServerFactory webServerFactory) {
if (this.managementServerProperties == null) {
this.managementServerProperties = BeanFactoryUtils
.beanOfTypeIncludingAncestors(this.beanFactory,
ManagementServerProperties.class);
this.server = BeanFactoryUtils.beanOfTypeIncludingAncestors(
this.beanFactory, ServerProperties.class);
this.serverCustomizer = BeanFactoryUtils.beanOfTypeIncludingAncestors(
this.beanFactory, DefaultServletWebServerFactoryCustomizer.class);
}
// Customize as per the parent context first (so e.g. the access logs go to
// the same place)
this.serverCustomizer.customize(webServerFactory);
// Then reset the error pages
webServerFactory.setErrorPages(Collections.<ErrorPage>emptySet());
// and the context path
protected void customize(ConfigurableServletWebServerFactory webServerFactory,
ManagementServerProperties managementServerProperties,
ServerProperties serverProperties) {
super.customize(webServerFactory, managementServerProperties,
serverProperties);
webServerFactory.setContextPath("");
// and add the management-specific bits
webServerFactory.setPort(this.managementServerProperties.getPort());
if (this.managementServerProperties.getSsl() != null) {
webServerFactory.setSsl(this.managementServerProperties.getSsl());
}
webServerFactory.setServerHeader(this.server.getServerHeader());
webServerFactory.setAddress(this.managementServerProperties.getAddress());
webServerFactory
.addErrorPages(new ErrorPage(this.server.getError().getPath()));
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.endpoint.infrastructure;
import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping;
/**
* Callback for customizing the {@link WebEndpointServletHandlerMapping} at configuration
* time.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
@FunctionalInterface
public interface WebEndpointHandlerMappingCustomizer {
/**
* Customize the given {@code mapping}.
* @param mapping the mapping to customize
*/
void customize(WebEndpointServletHandlerMapping mapping);
}

View File

@ -0,0 +1,146 @@
/*
* 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.endpoint.infrastructure;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointCorsProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.endpoint.web.WebEndpointOperation;
import org.springframework.boot.endpoint.web.jersey.JerseyEndpointResourceFactory;
import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping;
import org.springframework.boot.endpoint.web.reactive.WebEndpointReactiveHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet;
/**
* Management context configuration for the infrastructure for web endpoints.
*
* @author Andy Wilkinson
*/
@ConditionalOnWebApplication
@ManagementContextConfiguration
@EnableConfigurationProperties({ EndpointCorsProperties.class,
ManagementServerProperties.class })
class WebEndpointInfrastructureManagementContextConfiguration {
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(ResourceConfig.class)
@ConditionalOnBean(ResourceConfig.class)
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
static class JerseyWebEndpointConfiguration {
@Bean
public ResourceConfigCustomizer webEndpointRegistrar(
EndpointProvider<WebEndpointOperation> provider,
ManagementServerProperties managementServerProperties) {
return (resourceConfig) -> resourceConfig.registerResources(new HashSet<>(
new JerseyEndpointResourceFactory().createEndpointResources(
managementServerProperties.getContextPath(),
provider.getEndpoints())));
}
}
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnBean(DispatcherServlet.class)
static class MvcWebEndpointConfiguration {
private final List<WebEndpointHandlerMappingCustomizer> mappingCustomizers;
MvcWebEndpointConfiguration(
ObjectProvider<List<WebEndpointHandlerMappingCustomizer>> mappingCustomizers) {
this.mappingCustomizers = mappingCustomizers
.getIfUnique(Collections::emptyList);
}
@Bean
@ConditionalOnMissingBean
public WebEndpointServletHandlerMapping webEndpointServletHandlerMapping(
EndpointProvider<WebEndpointOperation> provider,
EndpointCorsProperties corsProperties,
ManagementServerProperties managementServerProperties) {
WebEndpointServletHandlerMapping handlerMapping = new WebEndpointServletHandlerMapping(
managementServerProperties.getContextPath(), provider.getEndpoints(),
getCorsConfiguration(corsProperties));
for (WebEndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
customizer.customize(handlerMapping);
}
return handlerMapping;
}
private CorsConfiguration getCorsConfiguration(
EndpointCorsProperties properties) {
if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
return null;
}
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(properties.getAllowedOrigins());
if (!CollectionUtils.isEmpty(properties.getAllowedHeaders())) {
configuration.setAllowedHeaders(properties.getAllowedHeaders());
}
if (!CollectionUtils.isEmpty(properties.getAllowedMethods())) {
configuration.setAllowedMethods(properties.getAllowedMethods());
}
if (!CollectionUtils.isEmpty(properties.getExposedHeaders())) {
configuration.setExposedHeaders(properties.getExposedHeaders());
}
if (properties.getMaxAge() != null) {
configuration.setMaxAge(properties.getMaxAge());
}
if (properties.getAllowCredentials() != null) {
configuration.setAllowCredentials(properties.getAllowCredentials());
}
return configuration;
}
}
@ConditionalOnWebApplication(type = Type.REACTIVE)
static class ReactiveWebEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
public WebEndpointReactiveHandlerMapping webEndpointReactiveHandlerMapping(
EndpointProvider<WebEndpointOperation> provider,
ManagementServerProperties managementServerProperties) {
return new WebEndpointReactiveHandlerMapping(
managementServerProperties.getContextPath(), provider.getEndpoints());
}
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2012-2015 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.
*/
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for the Actuator's endpoint infrastructure.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure;

View File

@ -0,0 +1,50 @@
/*
* 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.endpoint.jmx;
import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.actuate.endpoint.jmx.AuditEventsJmxEndpointExtension;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.endpoint.jmx.JmxEndpointExtension;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Auto-configuration for JMX {@link org.springframework.boot.endpoint.Endpoint Endpoints}
* and JMX-specific {@link JmxEndpointExtension endpoint extensions}.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
@AutoConfigureAfter(EndpointAutoConfiguration.class)
@Configuration
public class JmxEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(AuditEventsEndpoint.class)
public AuditEventsJmxEndpointExtension auditEventsJmxEndpointExtension(
AuditEventsEndpoint auditEventsEndpoint) {
return new AuditEventsJmxEndpointExtension(auditEventsEndpoint);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2015 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.
@ -15,6 +15,7 @@
*/
/**
* Auto-configuration for Jolokia.
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for the Actuator's JMX endpoints.
*/
package org.springframework.boot.actuate.autoconfigure.jolokia;
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;

View File

@ -0,0 +1,56 @@
/*
* 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.endpoint.support;
/**
* Determines if an endpoint is enabled or not.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public final class EndpointEnablement {
private final boolean enabled;
private final String reason;
/**
* Creates a new instance.
* @param enabled whether or not the endpoint is enabled
* @param reason a human readable reason of the decision
*/
EndpointEnablement(boolean enabled, String reason) {
this.enabled = enabled;
this.reason = reason;
}
/**
* Return whether or not the endpoint is enabled.
* @return {@code true} if the endpoint is enabled, {@code false} otherwise
*/
public boolean isEnabled() {
return this.enabled;
}
/**
* Return a human readable reason of the decision.
* @return the reason of the endpoint's enablement
*/
public String getReason() {
return this.reason;
}
}

View File

@ -0,0 +1,169 @@
/*
* 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.endpoint.support;
import org.springframework.boot.endpoint.EndpointType;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* Determines an endpoint's enablement based on the current {@link Environment}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class EndpointEnablementProvider {
private final Environment environment;
/**
* Creates a new instance with the {@link Environment} to use.
* @param environment the environment
*/
public EndpointEnablementProvider(Environment environment) {
this.environment = environment;
}
/**
* Return the {@link EndpointEnablement} of an endpoint with no specific tech
* exposure.
* @param endpointId the id of the endpoint
* @param enabledByDefault whether the endpoint is enabled by default or not
* @return the {@link EndpointEnablement} of that endpoint
*/
public EndpointEnablement getEndpointEnablement(String endpointId,
boolean enabledByDefault) {
return getEndpointEnablement(endpointId, enabledByDefault, null);
}
/**
* Return the {@link EndpointEnablement} of an endpoint for a specific tech exposure.
* @param endpointId the id of the endpoint
* @param enabledByDefault whether the endpoint is enabled by default or not
* @param endpointType the requested {@link EndpointType}
* @return the {@link EndpointEnablement} of that endpoint for the specified {@link EndpointType}
*/
public EndpointEnablement getEndpointEnablement(String endpointId,
boolean enabledByDefault, EndpointType endpointType) {
if (!StringUtils.hasText(endpointId)) {
throw new IllegalArgumentException("Endpoint id must have a value");
}
if (endpointId.equals("all")) {
throw new IllegalArgumentException("Endpoint id 'all' is a reserved value "
+ "and cannot be used by an endpoint");
}
if (endpointType != null) {
String endpointTypeKey = createTechSpecificKey(endpointId, endpointType);
EndpointEnablement endpointTypeSpecificOutcome = getEnablementFor(
endpointTypeKey);
if (endpointTypeSpecificOutcome != null) {
return endpointTypeSpecificOutcome;
}
}
else {
// If any tech specific is on at this point we should enable the endpoint
EndpointEnablement anyTechSpecificOutcome = getAnyTechSpecificOutcomeFor(
endpointId);
if (anyTechSpecificOutcome != null) {
return anyTechSpecificOutcome;
}
}
String endpointKey = createKey(endpointId, "enabled");
EndpointEnablement endpointSpecificOutcome = getEnablementFor(endpointKey);
if (endpointSpecificOutcome != null) {
return endpointSpecificOutcome;
}
// All endpoints specific attributes have been looked at. Checking default value
// for the endpoint
if (!enabledByDefault) {
return new EndpointEnablement(false,
createDefaultEnablementMessage(endpointId, enabledByDefault,
endpointType));
}
if (endpointType != null) {
String globalTypeKey = createTechSpecificKey("all", endpointType);
EndpointEnablement globalTypeOutcome = getEnablementFor(globalTypeKey);
if (globalTypeOutcome != null) {
return globalTypeOutcome;
}
}
else {
// Check if there is a global tech required
EndpointEnablement anyTechGeneralOutcome = getAnyTechSpecificOutcomeFor("all");
if (anyTechGeneralOutcome != null) {
return anyTechGeneralOutcome;
}
}
String globalKey = createKey("all", "enabled");
EndpointEnablement globalOutCome = getEnablementFor(globalKey);
if (globalOutCome != null) {
return globalOutCome;
}
return new EndpointEnablement(enabledByDefault, createDefaultEnablementMessage(
endpointId, enabledByDefault, endpointType));
}
private String createDefaultEnablementMessage(String endpointId,
boolean enabledByDefault, EndpointType endpointType) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("endpoint '%s' ", endpointId));
if (endpointType != null) {
sb.append(String.format("(%s) ", endpointType.name().toLowerCase()));
}
sb.append(String.format("is %s by default",
(enabledByDefault ? "enabled" : "disabled")));
return sb.toString();
}
private EndpointEnablement getAnyTechSpecificOutcomeFor(String endpointId) {
for (EndpointType endpointType : EndpointType.values()) {
String key = createTechSpecificKey(endpointId, endpointType);
EndpointEnablement outcome = getEnablementFor(key);
if (outcome != null && outcome.isEnabled()) {
return outcome;
}
}
return null;
}
private String createTechSpecificKey(String endpointId, EndpointType endpointType) {
return createKey(endpointId, endpointType.name().toLowerCase() + ".enabled");
}
private String createKey(String endpointId, String suffix) {
return "endpoints." + endpointId + "." + suffix;
}
/**
* Return an {@link EndpointEnablement} for the specified key if it is set or
* {@code null} if the key is not present in the environment.
* @param key the key to check
* @return the outcome or {@code null} if the key is no set
*/
private EndpointEnablement getEnablementFor(String key) {
if (this.environment.containsProperty(key)) {
boolean match = this.environment.getProperty(key, Boolean.class, true);
return new EndpointEnablement(match, String.format("found property %s", key));
}
return null;
}
}

View File

@ -15,6 +15,6 @@
*/
/**
* {@code @Condition} annotations and supporting classes.
* Support classes for the Actuator's endpoint auto-configuration.
*/
package org.springframework.boot.actuate.condition;
package org.springframework.boot.actuate.autoconfigure.endpoint.support;

View File

@ -14,35 +14,35 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.health;
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
/**
* Configuration properties for the {@link HealthMvcEndpoint}.
* Configuration properties for the {@link HealthWebEndpointExtension}.
*
* @author Christian Dupuis
* @author Andy Wilkinson
* @since 2.0.0
*/
@ConfigurationProperties(prefix = "endpoints.health")
public class HealthMvcEndpointProperties {
public class HealthWebEndpointExtensionProperties {
/**
* Mapping of health statuses to HttpStatus codes. By default, registered health
* statuses map to sensible defaults (i.e. UP maps to 200).
*/
private Map<String, HttpStatus> mapping = new HashMap<>();
private Map<String, Integer> mapping = new HashMap<>();
public Map<String, HttpStatus> getMapping() {
public Map<String, Integer> getMapping() {
return this.mapping;
}
public void setMapping(Map<String, HttpStatus> mapping) {
public void setMapping(Map<String, Integer> mapping) {
this.mapping = mapping;
}

View File

@ -0,0 +1,115 @@
/*
* 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.endpoint.web;
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.StringUtils;
/**
* Configuration for web-specific endpoint functionality.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
@ManagementContextConfiguration
@EnableConfigurationProperties(HealthWebEndpointExtensionProperties.class)
public class WebEndpointManagementContextConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public HeapDumpWebEndpoint heapDumpWebEndpoint() {
return new HeapDumpWebEndpoint();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(value = HealthEndpoint.class, search = SearchStrategy.CURRENT)
public HealthWebEndpointExtension healthWebEndpointExtension(HealthEndpoint delegate,
HealthWebEndpointExtensionProperties extensionProperties) {
HealthWebEndpointExtension webExtension = new HealthWebEndpointExtension(
delegate);
if (extensionProperties.getMapping() != null) {
webExtension.addStatusMapping(extensionProperties.getMapping());
}
return webExtension;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(value = AuditEventsEndpoint.class, search = SearchStrategy.CURRENT)
public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension(
AuditEventsEndpoint delegate) {
return new AuditEventsWebEndpointExtension(delegate);
}
@Bean
@ConditionalOnMissingBean
@Conditional(LogFileCondition.class)
public LogFileWebEndpoint logfileWebEndpoint(Environment environment) {
return new LogFileWebEndpoint(environment);
}
private static class LogFileCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String config = environment.resolvePlaceholders("${logging.file:}");
ConditionMessage.Builder message = ConditionMessage.forCondition("Log File");
if (StringUtils.hasText(config)) {
return ConditionOutcome
.match(message.found("logging.file").items(config));
}
config = environment.resolvePlaceholders("${logging.path:}");
if (StringUtils.hasText(config)) {
return ConditionOutcome
.match(message.found("logging.path").items(config));
}
config = environment.getProperty("endpoints.logfile.external-file");
if (StringUtils.hasText(config)) {
return ConditionOutcome.match(
message.found("endpoints.logfile.external-file").items(config));
}
return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll());
}
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2012-2015 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.
*/
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for the Actuator's web endpoints.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.web;

View File

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.autoconfigure.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
@ -28,9 +29,6 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.ManagementContextResolver;
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -51,6 +49,9 @@ import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.endpoint.EndpointInfo;
import org.springframework.boot.endpoint.web.WebEndpointOperation;
import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
@ -218,7 +219,8 @@ public class ManagementWebSecurityAutoConfiguration {
AuthenticationEntryPoint entryPoint = entryPoint();
http.exceptionHandling().authenticationEntryPoint(entryPoint);
// Match all the requests for actuator endpoints ...
http.requestMatcher(matcher);
http.requestMatcher(matcher).authorizeRequests().anyRequest()
.authenticated();
http.httpBasic().authenticationEntryPoint(entryPoint).and().cors();
// No cookies for management endpoints by default
http.csrf().disable();
@ -256,20 +258,21 @@ public class ManagementWebSecurityAutoConfiguration {
private static class EndpointPaths {
public String[] getPaths(EndpointHandlerMapping endpointHandlerMapping) {
public String[] getPaths(
WebEndpointServletHandlerMapping endpointHandlerMapping) {
if (endpointHandlerMapping == null) {
return NO_PATHS;
}
Set<? extends MvcEndpoint> endpoints = endpointHandlerMapping.getEndpoints();
Collection<EndpointInfo<WebEndpointOperation>> endpoints = endpointHandlerMapping
.getEndpoints();
Set<String> paths = new LinkedHashSet<>(endpoints.size());
for (MvcEndpoint endpoint : endpoints) {
String path = endpointHandlerMapping.getPath(endpoint.getPath());
for (EndpointInfo<WebEndpointOperation> endpoint : endpoints) {
String path = endpointHandlerMapping.getEndpointPath() + "/"
+ endpoint.getId();
paths.add(path);
if (!path.equals("")) {
paths.add(path + "/**");
// Add Spring MVC-generated additional paths
paths.add(path + ".*");
}
paths.add(path + "/**");
// Add Spring MVC-generated additional paths
paths.add(path + ".*");
paths.add(path + "/");
}
return paths.toArray(new String[paths.size()]);
@ -300,7 +303,7 @@ public class ManagementWebSecurityAutoConfiguration {
server.getServlet().getPath(path) + "/**");
return matcher;
}
// Match everything, including the sensitive and non-sensitive paths
// Match all endpoint paths
return new LazyEndpointPathRequestMatcher(contextResolver,
new EndpointPaths());
}
@ -323,7 +326,7 @@ public class ManagementWebSecurityAutoConfiguration {
ServerProperties server = this.contextResolver.getApplicationContext()
.getBean(ServerProperties.class);
List<RequestMatcher> matchers = new ArrayList<>();
EndpointHandlerMapping endpointHandlerMapping = getRequiredEndpointHandlerMapping();
WebEndpointServletHandlerMapping endpointHandlerMapping = getRequiredEndpointHandlerMapping();
for (String path : this.endpointPaths.getPaths(endpointHandlerMapping)) {
matchers.add(
new AntPathRequestMatcher(server.getServlet().getPath(path)));
@ -331,16 +334,18 @@ public class ManagementWebSecurityAutoConfiguration {
return (matchers.isEmpty() ? MATCH_NONE : new OrRequestMatcher(matchers));
}
private EndpointHandlerMapping getRequiredEndpointHandlerMapping() {
EndpointHandlerMapping endpointHandlerMapping = null;
private WebEndpointServletHandlerMapping getRequiredEndpointHandlerMapping() {
WebEndpointServletHandlerMapping endpointHandlerMapping = null;
ApplicationContext context = this.contextResolver.getApplicationContext();
if (context.getBeanNamesForType(EndpointHandlerMapping.class).length > 0) {
endpointHandlerMapping = context.getBean(EndpointHandlerMapping.class);
if (context.getBeanNamesForType(
WebEndpointServletHandlerMapping.class).length > 0) {
endpointHandlerMapping = context
.getBean(WebEndpointServletHandlerMapping.class);
}
if (endpointHandlerMapping == null) {
// Maybe there are actually no endpoints (e.g. management.port=-1)
endpointHandlerMapping = new EndpointHandlerMapping(
Collections.<NamedMvcEndpoint>emptySet());
endpointHandlerMapping = new WebEndpointServletHandlerMapping("",
Collections.emptySet());
}
return endpointHandlerMapping;
}

View File

@ -16,75 +16,62 @@
package org.springframework.boot.actuate.cloudfoundry;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.endpoint.mvc.AbstractMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* {@link MvcEndpoint} to expose HAL-formatted JSON for Cloud Foundry specific actuator
* endpoints.
*
* @author Madhura Bhave
*/
class CloudFoundryDiscoveryMvcEndpoint extends AbstractMvcEndpoint {
class CloudFoundryDiscoveryMvcEndpoint {
private final Set<NamedMvcEndpoint> endpoints;
// TODO Port to new infrastructure
CloudFoundryDiscoveryMvcEndpoint(Set<NamedMvcEndpoint> endpoints) {
super("");
this.endpoints = endpoints;
}
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Map<String, Link>> links(HttpServletRequest request) {
Map<String, Link> links = new LinkedHashMap<>();
String url = request.getRequestURL().toString();
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
links.put("self", Link.withHref(url));
AccessLevel accessLevel = AccessLevel.get(request);
for (NamedMvcEndpoint endpoint : this.endpoints) {
if (accessLevel != null && accessLevel.isAccessAllowed(endpoint.getPath())) {
links.put(endpoint.getName(),
Link.withHref(url + "/" + endpoint.getName()));
}
}
return Collections.singletonMap("_links", links);
}
/**
* Details for a link in the HAL response.
*/
static class Link {
private String href;
public String getHref() {
return this.href;
}
public void setHref(String href) {
this.href = href;
}
static Link withHref(Object href) {
Link link = new Link();
link.setHref(href.toString());
return link;
}
}
// private final Set<NamedMvcEndpoint> endpoints;
//
// CloudFoundryDiscoveryMvcEndpoint(Set<NamedMvcEndpoint> endpoints) {
// this.endpoints = endpoints;
// }
//
// @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
// @ResponseBody
// public Map<String, Map<String, Link>> links(HttpServletRequest request) {
// Map<String, Link> links = new LinkedHashMap<>();
// String url = request.getRequestURL().toString();
// if (url.endsWith("/")) {
// url = url.substring(0, url.length() - 1);
// }
// links.put("self", Link.withHref(url));
// AccessLevel accessLevel = AccessLevel.get(request);
// for (NamedMvcEndpoint endpoint : this.endpoints) {
// if (accessLevel != null && accessLevel.isAccessAllowed(endpoint.getPath())) {
// links.put(endpoint.getName(),
// Link.withHref(url + "/" + endpoint.getName()));
// }
// }
// return Collections.singletonMap("_links", links);
// }
//
// /**
// * Details for a link in the HAL response.
// */
// static class Link {
//
// private String href;
//
// public String getHref() {
// return this.href;
// }
//
// public void setHref(String href) {
// this.href = href;
// }
//
// static Link withHref(Object href) {
// Link link = new Link();
// link.setHref(href.toString());
// return link;
// }
//
// }
}

View File

@ -1,78 +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.cloudfoundry;
import java.util.Iterator;
import java.util.Set;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
/**
* {@link HandlerMapping} to map {@link Endpoint}s to Cloud Foundry specific URLs.
*
* @author Madhura Bhave
* @since 2.0.0
*/
public class CloudFoundryEndpointHandlerMapping
extends AbstractEndpointHandlerMapping<NamedMvcEndpoint> {
public CloudFoundryEndpointHandlerMapping(Set<? extends NamedMvcEndpoint> endpoints,
CorsConfiguration corsConfiguration, HandlerInterceptor securityInterceptor) {
super(endpoints, corsConfiguration);
setSecurityInterceptor(securityInterceptor);
}
@Override
protected void postProcessEndpoints(Set<NamedMvcEndpoint> endpoints) {
super.postProcessEndpoints(endpoints);
Iterator<NamedMvcEndpoint> iterator = endpoints.iterator();
HealthMvcEndpoint healthMvcEndpoint = null;
while (iterator.hasNext()) {
NamedMvcEndpoint endpoint = iterator.next();
if (endpoint instanceof HealthMvcEndpoint) {
iterator.remove();
healthMvcEndpoint = (HealthMvcEndpoint) endpoint;
}
}
if (healthMvcEndpoint != null) {
endpoints.add(
new CloudFoundryHealthMvcEndpoint(healthMvcEndpoint.getDelegate()));
}
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
detectHandlerMethods(new CloudFoundryDiscoveryMvcEndpoint(getEndpoints()));
}
@Override
protected String getPath(MvcEndpoint endpoint) {
if (endpoint instanceof NamedMvcEndpoint) {
return "/" + ((NamedMvcEndpoint) endpoint).getName();
}
return super.getPath(endpoint);
}
}

View File

@ -16,31 +16,29 @@
package org.springframework.boot.actuate.cloudfoundry;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
/**
* Extension of {@link HealthMvcEndpoint} for Cloud Foundry. Since security for Cloud
* Foundry actuators is already handled by the {@link CloudFoundrySecurityInterceptor},
* this endpoint skips the additional security checks done by the regular
* {@link HealthMvcEndpoint}.
* Extension of {@link HealthWebEndpointExtension} for Cloud Foundry. Since security for
* Cloud Foundry actuators is already handled by the
* {@link CloudFoundrySecurityInterceptor}, this endpoint skips the additional security
* checks done by the regular {@link HealthWebEndpointExtension}.
*
* @author Madhura Bhave
*/
class CloudFoundryHealthMvcEndpoint extends HealthMvcEndpoint {
class CloudFoundryHealthMvcEndpoint extends HealthWebEndpointExtension {
// TODO Port to new infrastructure
CloudFoundryHealthMvcEndpoint(HealthEndpoint delegate) {
super(delegate);
}
@Override
protected boolean exposeHealthDetails(HttpServletRequest request,
Principal principal) {
return true;
}
//
// @Override
// protected boolean exposeHealthDetails(HttpServletRequest request,
// Principal principal) {
// return true;
// }
}

View File

@ -23,12 +23,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
@ -57,52 +51,54 @@ public class CloudFoundrySecurityInterceptor extends HandlerInterceptorAdapter {
this.applicationId = applicationId;
}
// TODO Port to new infrastructure
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (CorsUtils.isPreFlightRequest(request)) {
return true;
}
try {
if (!StringUtils.hasText(this.applicationId)) {
throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
"Application id is not available");
}
if (this.cloudFoundrySecurityService == null) {
throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
"Cloud controller URL is not available");
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
if (HttpMethod.OPTIONS.matches(request.getMethod())
&& !(handlerMethod.getBean() instanceof MvcEndpoint)) {
return true;
}
MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean();
check(request, mvcEndpoint);
}
catch (CloudFoundryAuthorizationException ex) {
logger.error(ex);
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.getWriter()
.write("{\"security_error\":\"" + ex.getMessage() + "\"}");
response.setStatus(ex.getStatusCode().value());
return false;
}
// if (CorsUtils.isPreFlightRequest(request)) {
// return true;
// }
// try {
// if (!StringUtils.hasText(this.applicationId)) {
// throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
// "Application id is not available");
// }
// if (this.cloudFoundrySecurityService == null) {
// throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
// "Cloud controller URL is not available");
// }
// HandlerMethod handlerMethod = (HandlerMethod) handler;
// if (HttpMethod.OPTIONS.matches(request.getMethod())
// && !(handlerMethod.getBean() instanceof MvcEndpoint)) {
// return true;
// }
// MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean();
// check(request, mvcEndpoint);
// }
// catch (CloudFoundryAuthorizationException ex) {
// logger.error(ex);
// response.setContentType(MediaType.APPLICATION_JSON.toString());
// response.getWriter()
// .write("{\"security_error\":\"" + ex.getMessage() + "\"}");
// response.setStatus(ex.getStatusCode().value());
// return false;
// }
return true;
}
private void check(HttpServletRequest request, MvcEndpoint mvcEndpoint)
throws Exception {
Token token = getToken(request);
this.tokenValidator.validate(token);
AccessLevel accessLevel = this.cloudFoundrySecurityService
.getAccessLevel(token.toString(), this.applicationId);
if (!accessLevel.isAccessAllowed(mvcEndpoint.getPath())) {
throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
"Access denied");
}
accessLevel.put(request);
}
// private void check(HttpServletRequest request, MvcEndpoint mvcEndpoint)
// throws Exception {
// Token token = getToken(request);
// this.tokenValidator.validate(token);
// AccessLevel accessLevel = this.cloudFoundrySecurityService
// .getAccessLevel(token.toString(), this.applicationId);
// if (!accessLevel.isAccessAllowed(mvcEndpoint.getPath())) {
// throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
// "Access denied");
// }
// accessLevel.put(request);
// }
private Token getToken(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");

View File

@ -1,55 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.condition;
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 org.springframework.context.annotation.Conditional;
/**
* {@link Conditional} that checks whether or not an endpoint is enabled. Matches if the
* value of the {@code endpoints.<name>.enabled} property is {@code true}. Does not match
* if the property's value or {@code enabledByDefault} is {@code false}. Otherwise,
* matches if the value of the {@code endpoints.enabled} property is {@code true} or if
* the property is not configured.
*
* @author Andy Wilkinson
* @since 1.2.4
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
@Conditional(OnEnabledEndpointCondition.class)
@Documented
public @interface ConditionalOnEnabledEndpoint {
/**
* The name of the endpoint.
* @return The name of the endpoint
*/
String value();
/**
* Returns whether or not the endpoint is enabled by default.
* @return {@code true} if the endpoint is enabled by default, otherwise {@code false}
*/
boolean enabledByDefault() default true;
}

View File

@ -1,77 +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.condition;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* {@link Condition} that checks whether or not an endpoint is enabled.
*
* @author Andy Wilkinson
* @author Madhura Bhave
*/
class OnEnabledEndpointCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(metadata
.getAnnotationAttributes(ConditionalOnEnabledEndpoint.class.getName()));
String endpointName = annotationAttributes.getString("value");
boolean enabledByDefault = annotationAttributes.getBoolean("enabledByDefault");
ConditionOutcome outcome = determineEndpointOutcome(endpointName,
enabledByDefault, context);
if (outcome != null) {
return outcome;
}
return determineAllEndpointsOutcome(context);
}
private ConditionOutcome determineEndpointOutcome(String endpointName,
boolean enabledByDefault, ConditionContext context) {
Environment environment = context.getEnvironment();
String enabledProperty = "endpoints." + endpointName + ".enabled";
if (environment.containsProperty(enabledProperty) || !enabledByDefault) {
boolean match = environment.getProperty(enabledProperty, Boolean.class,
enabledByDefault);
ConditionMessage message = ConditionMessage
.forCondition(ConditionalOnEnabledEndpoint.class,
"(" + endpointName + ")")
.because(match ? "enabled" : "disabled");
return new ConditionOutcome(match, message);
}
return null;
}
private ConditionOutcome determineAllEndpointsOutcome(ConditionContext context) {
boolean match = Boolean.valueOf(
context.getEnvironment().getProperty("endpoints.enabled", "true"));
ConditionMessage message = ConditionMessage
.forCondition(ConditionalOnEnabledEndpoint.class)
.because("All endpoints are " + (match ? "enabled" : "disabled")
+ " by default");
return new ConditionOutcome(match, message);
}
}

View File

@ -1,98 +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.endpoint;
import java.util.regex.Pattern;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
* Abstract base for {@link Endpoint} implementations.
*
* @param <T> the endpoint data type
* @author Phillip Webb
* @author Christian Dupuis
*/
public abstract class AbstractEndpoint<T> implements Endpoint<T>, EnvironmentAware {
private static final Pattern ID_PATTERN = Pattern.compile("\\w+");
private Environment environment;
/**
* Endpoint identifier. With HTTP monitoring the identifier of the endpoint is mapped
* to a URL (e.g. 'foo' is mapped to '/foo').
*/
private String id;
/**
* Enable the endpoint.
*/
private Boolean enabled;
/**
* Create a new endpoint instance. The endpoint will enabled flag will be based on the
* spring {@link Environment} unless explicitly set.
* @param id the endpoint ID
*/
public AbstractEndpoint(String id) {
setId(id);
}
/**
* Create a new endpoint instance.
* @param id the endpoint ID
* @param enabled if the endpoint is enabled or not.
*/
public AbstractEndpoint(String id, boolean enabled) {
setId(id);
this.enabled = enabled;
}
protected final Environment getEnvironment() {
return this.environment;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public String getId() {
return this.id;
}
public void setId(String id) {
Assert.notNull(id, "Id must not be null");
Assert.isTrue(ID_PATTERN.matcher(id).matches(),
"Id must only contains letters, numbers and '_'");
this.id = id;
}
@Override
public boolean isEnabled() {
return EndpointProperties.isEnabled(this.environment, this.enabled);
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.endpoint;
import java.util.Date;
import java.util.List;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.util.Assert;
/**
* {@link Endpoint} to expose audit events.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
@Endpoint(id = "auditevents")
public class AuditEventsEndpoint {
private final AuditEventRepository auditEventRepository;
public AuditEventsEndpoint(AuditEventRepository auditEventRepository) {
Assert.notNull(auditEventRepository, "AuditEventRepository must not be null");
this.auditEventRepository = auditEventRepository;
}
@ReadOperation
public List<AuditEvent> eventsWithPrincipalDateAfterAndType(String principal,
Date after, String type) {
return this.auditEventRepository.find(principal, after, type);
}
}

View File

@ -26,13 +26,12 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint.Report;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.context.annotation.Condition;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
@ -47,19 +46,19 @@ import org.springframework.util.StringUtils;
* @author Dave Syer
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "endpoints.autoconfig")
public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> {
@Endpoint(id = "autoconfig")
public class AutoConfigurationReportEndpoint {
@Autowired
private ConditionEvaluationReport autoConfigurationReport;
private final ConditionEvaluationReport conditionEvaluationReport;
public AutoConfigurationReportEndpoint() {
super("autoconfig");
public AutoConfigurationReportEndpoint(
ConditionEvaluationReport conditionEvaluationReport) {
this.conditionEvaluationReport = conditionEvaluationReport;
}
@Override
public Report invoke() {
return new Report(this.autoConfigurationReport);
@ReadOperation
public Report getEvaluationReport() {
return new Report(this.conditionEvaluationReport);
}
/**

View File

@ -21,7 +21,8 @@ import java.util.List;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.context.ApplicationContext;
@ -40,18 +41,13 @@ import org.springframework.util.Assert;
* @author Dave Syer
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "endpoints.beans")
public class BeansEndpoint extends AbstractEndpoint<List<Object>>
implements ApplicationContextAware {
@Endpoint(id = "beans")
public class BeansEndpoint implements ApplicationContextAware {
private final HierarchyAwareLiveBeansView liveBeansView = new HierarchyAwareLiveBeansView();
private final JsonParser parser = JsonParserFactory.getJsonParser();
public BeansEndpoint() {
super("beans");
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (context.getEnvironment()
@ -60,8 +56,8 @@ public class BeansEndpoint extends AbstractEndpoint<List<Object>>
}
}
@Override
public List<Object> invoke() {
@ReadOperation
public List<Object> beans() {
return this.parser.parseList(this.liveBeansView.getSnapshotAsJson());
}

View File

@ -42,6 +42,8 @@ import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ClassUtils;
@ -61,9 +63,9 @@ import org.springframework.util.StringUtils;
* @author Dave Syer
* @author Stephane Nicoll
*/
@ConfigurationProperties(prefix = "endpoints.configprops")
public class ConfigurationPropertiesReportEndpoint
extends AbstractEndpoint<Map<String, Object>> implements ApplicationContextAware {
@Endpoint(id = "configprops")
@ConfigurationProperties("endpoints.configprops")
public class ConfigurationPropertiesReportEndpoint implements ApplicationContextAware {
private static final String CGLIB_FILTER_ID = "cglibFilter";
@ -71,10 +73,6 @@ public class ConfigurationPropertiesReportEndpoint
private ApplicationContext context;
public ConfigurationPropertiesReportEndpoint() {
super("configprops");
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
@ -84,8 +82,8 @@ public class ConfigurationPropertiesReportEndpoint
this.sanitizer.setKeysToSanitize(keysToSanitize);
}
@Override
public Map<String, Object> invoke() {
@ReadOperation
public Map<String, Object> configurationProperties() {
return extract(this.context);
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2012-2015 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;
/**
* An endpoint that can be used to expose useful information to operations. Usually
* exposed via Spring MVC but could also be exposed using some other technique. Consider
* extending {@link AbstractEndpoint} if you are developing your own endpoint.
*
* @param <T> the endpoint data type
* @author Phillip Webb
* @author Dave Syer
* @author Christian Dupuis
* @see AbstractEndpoint
*/
public interface Endpoint<T> {
/**
* The logical ID of the endpoint. Must only contain simple letters, numbers and '_'
* characters (i.e. a {@literal "\w"} regex).
* @return the endpoint ID
*/
String getId();
/**
* Return if the endpoint is enabled.
* @return if the endpoint is enabled
*/
boolean isEnabled();
/**
* Called to invoke the endpoint.
* @return the results of the invocation
*/
T invoke();
}

View File

@ -21,11 +21,15 @@ import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint.EnvironmentDescriptor.PropertySourceDescriptor.PropertyValueDescriptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.Selector;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
@ -37,6 +41,9 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* {@link Endpoint} to expose {@link ConfigurableEnvironment environment} information.
@ -46,42 +53,54 @@ import org.springframework.core.env.StandardEnvironment;
* @author Christian Dupuis
* @author Madhura Bhave
*/
@ConfigurationProperties(prefix = "endpoints.env")
public class EnvironmentEndpoint extends AbstractEndpoint<EnvironmentDescriptor> {
@Endpoint(id = "env")
public class EnvironmentEndpoint {
private final Sanitizer sanitizer = new Sanitizer();
/**
* Create a new {@link EnvironmentEndpoint} instance.
*/
public EnvironmentEndpoint() {
super("env");
private final Environment environment;
public EnvironmentEndpoint(Environment environment) {
this.environment = environment;
}
public void setKeysToSanitize(String... keysToSanitize) {
this.sanitizer.setKeysToSanitize(keysToSanitize);
}
@Override
public EnvironmentDescriptor invoke() {
@ReadOperation
public EnvironmentDescriptor environment(String pattern) {
if (StringUtils.hasText(pattern)) {
return environment(Pattern.compile(pattern).asPredicate());
}
return environment((name) -> true);
}
@ReadOperation
public Object getEnvironmentEntry(@Selector String toMatch) {
return environment((name) -> toMatch.equals(name));
}
private EnvironmentDescriptor environment(Predicate<String> propertyNamePredicate) {
PropertyResolver resolver = getResolver();
List<PropertySourceDescriptor> propertySources = new ArrayList<>();
getPropertySourcesAsMap().forEach((sourceName, source) -> {
if (source instanceof EnumerablePropertySource) {
propertySources.add(describeSource(sourceName,
(EnumerablePropertySource<?>) source, resolver));
propertySources.add(
describeSource(sourceName, (EnumerablePropertySource<?>) source,
resolver, propertyNamePredicate));
}
});
return new EnvironmentDescriptor(
Arrays.asList(getEnvironment().getActiveProfiles()), propertySources);
Arrays.asList(this.environment.getActiveProfiles()), propertySources);
}
private PropertySourceDescriptor describeSource(String sourceName,
EnumerablePropertySource<?> source, PropertyResolver resolver) {
EnumerablePropertySource<?> source, PropertyResolver resolver,
Predicate<String> namePredicate) {
Map<String, PropertyValueDescriptor> properties = new LinkedHashMap<>();
for (String name : source.getPropertyNames()) {
properties.put(name, describeValueOf(name, source, resolver));
}
Stream.of(source.getPropertyNames()).filter(namePredicate).forEach(
(name) -> properties.put(name, describeValueOf(name, source, resolver)));
return new PropertySourceDescriptor(sourceName, properties);
}
@ -94,7 +113,7 @@ public class EnvironmentEndpoint extends AbstractEndpoint<EnvironmentDescriptor>
return new PropertyValueDescriptor(sanitize(name, resolved), origin);
}
public PropertyResolver getResolver() {
private PropertyResolver getResolver() {
PlaceholderSanitizingPropertyResolver resolver = new PlaceholderSanitizingPropertyResolver(
getPropertySources(), this.sanitizer);
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
@ -111,9 +130,8 @@ public class EnvironmentEndpoint extends AbstractEndpoint<EnvironmentDescriptor>
private MutablePropertySources getPropertySources() {
MutablePropertySources sources;
Environment environment = getEnvironment();
if (environment != null && environment instanceof ConfigurableEnvironment) {
sources = ((ConfigurableEnvironment) environment).getPropertySources();
if (this.environment instanceof ConfigurableEnvironment) {
sources = ((ConfigurableEnvironment) this.environment).getPropertySources();
}
else {
sources = new StandardEnvironment().getPropertySources();
@ -239,4 +257,17 @@ public class EnvironmentEndpoint extends AbstractEndpoint<EnvironmentDescriptor>
}
}
/**
* Exception thrown when the specified property cannot be found.
*/
@SuppressWarnings("serial")
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such property")
public static class NoSuchPropertyException extends RuntimeException {
public NoSuchPropertyException(String string) {
super(string);
}
}
}

View File

@ -28,8 +28,8 @@ import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.MigrationState;
import org.flywaydb.core.api.MigrationType;
import org.springframework.boot.actuate.endpoint.FlywayEndpoint.FlywayReport;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.util.Assert;
/**
@ -40,19 +40,18 @@ import org.springframework.util.Assert;
* @author Andy Wilkinson
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "endpoints.flyway")
public class FlywayEndpoint extends AbstractEndpoint<Map<String, FlywayReport>> {
@Endpoint(id = "flyway")
public class FlywayEndpoint {
private final Map<String, Flyway> flyways;
public FlywayEndpoint(Map<String, Flyway> flyways) {
super("flyway");
Assert.notEmpty(flyways, "Flyways must be specified");
this.flyways = flyways;
}
@Override
public Map<String, FlywayReport> invoke() {
@ReadOperation
public Map<String, FlywayReport> flywayReports() {
Map<String, FlywayReport> reports = new HashMap<>();
for (Map.Entry<String, Flyway> entry : this.flyways.entrySet()) {
reports.put(entry.getKey(),

View File

@ -22,7 +22,8 @@ import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.util.Assert;
/**
@ -32,16 +33,11 @@ import org.springframework.util.Assert;
* @author Christian Dupuis
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "endpoints.health")
public class HealthEndpoint extends AbstractEndpoint<Health> {
@Endpoint(id = "health")
public class HealthEndpoint {
private final HealthIndicator healthIndicator;
/**
* Time to live for cached result, in milliseconds.
*/
private long timeToLive = 1000;
/**
* Create a new {@link HealthEndpoint} instance.
* @param healthAggregator the health aggregator
@ -49,7 +45,6 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
*/
public HealthEndpoint(HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) {
super("health");
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
@ -60,28 +55,8 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
this.healthIndicator = healthIndicator;
}
/**
* Time to live for cached result. This is particularly useful to cache the result of
* this endpoint to prevent a DOS attack if it is accessed anonymously.
* @return time to live in milliseconds (default 1000)
*/
public long getTimeToLive() {
return this.timeToLive;
}
/**
* Set the time to live for cached results.
* @param timeToLive the time to live in milliseconds
*/
public void setTimeToLive(long timeToLive) {
this.timeToLive = timeToLive;
}
/**
* Invoke all {@link HealthIndicator} delegates and collect their health information.
*/
@Override
public Health invoke() {
@ReadOperation
public Health health() {
return this.healthIndicator.health();
}

View File

@ -21,7 +21,8 @@ import java.util.Map;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.util.Assert;
/**
@ -31,8 +32,8 @@ import org.springframework.util.Assert;
* @author Meang Akira Tanaka
* @author Stephane Nicoll
*/
@ConfigurationProperties(prefix = "endpoints.info")
public class InfoEndpoint extends AbstractEndpoint<Map<String, Object>> {
@Endpoint(id = "info")
public class InfoEndpoint {
private final List<InfoContributor> infoContributors;
@ -41,13 +42,12 @@ public class InfoEndpoint extends AbstractEndpoint<Map<String, Object>> {
* @param infoContributors the info contributors to use
*/
public InfoEndpoint(List<InfoContributor> infoContributors) {
super("info");
Assert.notNull(infoContributors, "Info contributors must not be null");
this.infoContributors = infoContributors;
}
@Override
public Map<String, Object> invoke() {
@ReadOperation
public Map<String, Object> info() {
Info.Builder builder = new Info.Builder();
for (InfoContributor contributor : this.infoContributors) {
contributor.contribute(builder);

View File

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.endpoint;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -35,8 +34,8 @@ import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.integration.spring.SpringLiquibase;
import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint.LiquibaseReport;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -46,23 +45,18 @@ import org.springframework.util.StringUtils;
* @author Eddú Meléndez
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "endpoints.liquibase")
public class LiquibaseEndpoint extends AbstractEndpoint<Map<String, LiquibaseReport>> {
@Endpoint(id = "liquibase")
public class LiquibaseEndpoint {
private final Map<String, SpringLiquibase> liquibases;
public LiquibaseEndpoint(SpringLiquibase liquibase) {
this(Collections.singletonMap("default", liquibase));
}
public LiquibaseEndpoint(Map<String, SpringLiquibase> liquibases) {
super("liquibase");
Assert.notEmpty(liquibases, "Liquibases must be specified");
this.liquibases = liquibases;
}
@Override
public Map<String, LiquibaseReport> invoke() {
@ReadOperation
public Map<String, LiquibaseReport> liquibaseReports() {
Map<String, LiquibaseReport> reports = new HashMap<>();
DatabaseFactory factory = DatabaseFactory.getInstance();
StandardChangeLogHistoryService service = new StandardChangeLogHistoryService();

View File

@ -24,7 +24,10 @@ import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.Selector;
import org.springframework.boot.endpoint.WriteOperation;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggingSystem;
@ -37,8 +40,8 @@ import org.springframework.util.Assert;
* @author Phillip Webb
* @since 1.5.0
*/
@ConfigurationProperties(prefix = "endpoints.loggers")
public class LoggersEndpoint extends AbstractEndpoint<Map<String, Object>> {
@Endpoint(id = "loggers")
public class LoggersEndpoint {
private final LoggingSystem loggingSystem;
@ -47,13 +50,12 @@ public class LoggersEndpoint extends AbstractEndpoint<Map<String, Object>> {
* @param loggingSystem the logging system to expose
*/
public LoggersEndpoint(LoggingSystem loggingSystem) {
super("loggers");
Assert.notNull(loggingSystem, "LoggingSystem must not be null");
this.loggingSystem = loggingSystem;
}
@Override
public Map<String, Object> invoke() {
@ReadOperation
public Map<String, Object> loggers() {
Collection<LoggerConfiguration> configurations = this.loggingSystem
.getLoggerConfigurations();
if (configurations == null) {
@ -65,6 +67,20 @@ public class LoggersEndpoint extends AbstractEndpoint<Map<String, Object>> {
return result;
}
@ReadOperation
public LoggerLevels loggerLevels(@Selector String name) {
Assert.notNull(name, "Name must not be null");
LoggerConfiguration configuration = this.loggingSystem
.getLoggerConfiguration(name);
return (configuration == null ? null : new LoggerLevels(configuration));
}
@WriteOperation
public void configureLogLevel(@Selector String name, LogLevel configuredLevel) {
Assert.notNull(name, "Name must not be empty");
this.loggingSystem.setLogLevel(name, configuredLevel);
}
private NavigableSet<LogLevel> getLevels() {
Set<LogLevel> levels = this.loggingSystem.getSupportedLogLevels();
return new TreeSet<>(levels).descendingSet();
@ -79,18 +95,6 @@ public class LoggersEndpoint extends AbstractEndpoint<Map<String, Object>> {
return loggers;
}
public LoggerLevels invoke(String name) {
Assert.notNull(name, "Name must not be null");
LoggerConfiguration configuration = this.loggingSystem
.getLoggerConfiguration(name);
return (configuration == null ? null : new LoggerLevels(configuration));
}
public void setLogLevel(String name, LogLevel level) {
Assert.notNull(name, "Name must not be empty");
this.loggingSystem.setLogLevel(name, level);
}
/**
* Levels configured for a given logger exposed in a JSON friendly way.
*/

View File

@ -22,19 +22,24 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.Selector;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link Endpoint} to expose a collection of {@link PublicMetrics}.
*
* @author Dave Syer
*/
@ConfigurationProperties(prefix = "endpoints.metrics")
public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
@Endpoint(id = "metrics")
public class MetricsEndpoint {
private final List<PublicMetrics> publicMetrics;
@ -52,7 +57,6 @@ public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
* {@link AnnotationAwareOrderComparator}.
*/
public MetricsEndpoint(Collection<PublicMetrics> publicMetrics) {
super("metrics");
Assert.notNull(publicMetrics, "PublicMetrics must not be null");
this.publicMetrics = new ArrayList<>(publicMetrics);
AnnotationAwareOrderComparator.sort(this.publicMetrics);
@ -67,14 +71,31 @@ public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
this.publicMetrics.remove(metrics);
}
@Override
public Map<String, Object> invoke() {
@ReadOperation
public Map<String, Object> metrics(String pattern) {
return metrics(StringUtils.hasText(pattern)
? Pattern.compile(pattern).asPredicate() : (name) -> true);
}
@ReadOperation
public Map<String, Object> metricNamed(@Selector String requiredName) {
Map<String, Object> metrics = metrics((name) -> name.equals(requiredName));
if (metrics.isEmpty()) {
return null;
}
return metrics;
}
private Map<String, Object> metrics(Predicate<String> namePredicate) {
Map<String, Object> result = new LinkedHashMap<>();
List<PublicMetrics> metrics = new ArrayList<>(this.publicMetrics);
for (PublicMetrics publicMetric : metrics) {
try {
for (Metric<?> metric : publicMetric.metrics()) {
result.put(metric.getName(), metric.getValue());
if (namePredicate.test(metric.getName())
&& metric.getValue() != null) {
result.put(metric.getName(), metric.getValue());
}
}
}
catch (Exception ex) {

View File

@ -44,7 +44,7 @@ public class MetricsEndpointMetricReader implements MetricReader {
@Override
public Metric<?> findOne(String metricName) {
Metric<Number> metric = null;
Object value = this.endpoint.invoke().get(metricName);
Object value = this.endpoint.metrics(null).get(metricName);
if (value != null) {
metric = new Metric<>(metricName, (Number) value);
}
@ -54,7 +54,7 @@ public class MetricsEndpointMetricReader implements MetricReader {
@Override
public Iterable<Metric<?>> findAll() {
List<Metric<?>> metrics = new ArrayList<>();
Map<String, Object> values = this.endpoint.invoke();
Map<String, Object> values = this.endpoint.metrics(null);
Date timestamp = new Date();
for (Entry<String, Object> entry : values.entrySet()) {
String name = entry.getKey();
@ -66,7 +66,7 @@ public class MetricsEndpointMetricReader implements MetricReader {
@Override
public long count() {
return this.endpoint.invoke().size();
return this.endpoint.metrics(null).size();
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
package org.springframework.boot.actuate.endpoint;
import java.util.HashMap;
import java.util.LinkedHashMap;
@ -32,8 +32,7 @@ import java.util.regex.PatternSyntaxException;
* @author Phillip Webb
* @author Sergei Egorov
* @author Andy Wilkinson
* @author Dylian Bego
* @since 1.3.0
* @since 2.0.0
*/
abstract class NamePatternFilter<T> {

View File

@ -25,7 +25,8 @@ import java.util.Map.Entry;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.method.HandlerMethod;
@ -38,9 +39,8 @@ import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
* @author Dave Syer
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "endpoints.mappings")
public class RequestMappingEndpoint extends AbstractEndpoint<Map<String, Object>>
implements ApplicationContextAware {
@Endpoint(id = "mappings")
public class RequestMappingEndpoint implements ApplicationContextAware {
private List<AbstractUrlHandlerMapping> handlerMappings = Collections.emptyList();
@ -49,10 +49,6 @@ public class RequestMappingEndpoint extends AbstractEndpoint<Map<String, Object>
private ApplicationContext applicationContext;
public RequestMappingEndpoint() {
super("mappings");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
@ -75,8 +71,8 @@ public class RequestMappingEndpoint extends AbstractEndpoint<Map<String, Object>
this.methodMappings = methodMappings;
}
@Override
public Map<String, Object> invoke() {
@ReadOperation
public Map<String, Object> mappings() {
Map<String, Object> result = new LinkedHashMap<>();
extractHandlerMappings(this.handlerMappings, result);
extractHandlerMappings(this.applicationContext, result);

View File

@ -20,7 +20,8 @@ import java.util.Collections;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.WriteOperation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
@ -32,9 +33,8 @@ import org.springframework.context.ConfigurableApplicationContext;
* @author Christian Dupuis
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "endpoints.shutdown")
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
implements ApplicationContextAware {
@Endpoint(id = "shutdown", enabledByDefault = false)
public class ShutdownEndpoint implements ApplicationContextAware {
private static final Map<String, Object> NO_CONTEXT_MESSAGE = Collections
.unmodifiableMap(Collections.<String, Object>singletonMap("message",
@ -46,15 +46,8 @@ public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
private ConfigurableApplicationContext context;
/**
* Create a new {@link ShutdownEndpoint} instance.
*/
public ShutdownEndpoint() {
super("shutdown", false);
}
@Override
public Map<String, Object> invoke() {
@WriteOperation
public Map<String, Object> shutdown() {
if (this.context == null) {
return NO_CONTEXT_MESSAGE;
}

View File

@ -21,25 +21,19 @@ import java.lang.management.ThreadInfo;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
/**
* {@link Endpoint} to expose thread info.
*
* @author Dave Syer
*/
@ConfigurationProperties(prefix = "endpoints.dump")
public class DumpEndpoint extends AbstractEndpoint<List<ThreadInfo>> {
@Endpoint(id = "threaddump")
public class ThreadDumpEndpoint {
/**
* Create a new {@link DumpEndpoint} instance.
*/
public DumpEndpoint() {
super("dump");
}
@Override
public List<ThreadInfo> invoke() {
@ReadOperation
public List<ThreadInfo> threadDump() {
return Arrays
.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true));
}

View File

@ -20,7 +20,8 @@ import java.util.List;
import org.springframework.boot.actuate.trace.Trace;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.util.Assert;
/**
@ -28,8 +29,8 @@ import org.springframework.util.Assert;
*
* @author Dave Syer
*/
@ConfigurationProperties(prefix = "endpoints.trace")
public class TraceEndpoint extends AbstractEndpoint<List<Trace>> {
@Endpoint(id = "trace")
public class TraceEndpoint {
private final TraceRepository repository;
@ -38,13 +39,12 @@ public class TraceEndpoint extends AbstractEndpoint<List<Trace>> {
* @param repository the trace repository
*/
public TraceEndpoint(TraceRepository repository) {
super("trace");
Assert.notNull(repository, "Repository must not be null");
this.repository = repository;
}
@Override
public List<Trace> invoke() {
@ReadOperation
public List<Trace> traces() {
return this.repository.findAll();
}

View File

@ -1,88 +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.endpoint.jmx;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.EndpointProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.ObjectUtils;
/**
* Abstract base class for {@link JmxEndpoint} implementations without a backing
* {@link Endpoint}.
*
* @author Vedran Pavic
* @author Phillip Webb
* @since 1.5.0
*/
public abstract class AbstractJmxEndpoint implements JmxEndpoint, EnvironmentAware {
private final DataConverter dataConverter;
private Environment environment;
/**
* Enable the endpoint.
*/
private Boolean enabled;
public AbstractJmxEndpoint(ObjectMapper objectMapper) {
this.dataConverter = new DataConverter(objectMapper);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
protected final Environment getEnvironment() {
return this.environment;
}
@Override
public boolean isEnabled() {
return EndpointProperties.isEnabled(this.environment, this.enabled);
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
@Override
public String getIdentity() {
return ObjectUtils.getIdentityHexString(this);
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {
return null;
}
/**
* Convert the given data into JSON.
* @param data the source data
* @return the JSON representation
*/
protected Object convert(Object data) {
return this.dataConverter.convert(data);
}
}

View File

@ -1,86 +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.endpoint.jmx;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link JmxEndpoint} for {@link AuditEventRepository}.
*
* @author Vedran Pavic
* @since 1.5.0
*/
@ConfigurationProperties(prefix = "endpoints.auditevents")
public class AuditEventsJmxEndpoint extends AbstractJmxEndpoint {
private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
private final AuditEventRepository auditEventRepository;
public AuditEventsJmxEndpoint(ObjectMapper objectMapper,
AuditEventRepository auditEventRepository) {
super(objectMapper);
Assert.notNull(auditEventRepository, "AuditEventRepository must not be null");
this.auditEventRepository = auditEventRepository;
}
@ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria")
public Object getData(String dateAfter) {
List<AuditEvent> auditEvents = this.auditEventRepository
.find(parseDate(dateAfter));
return convert(auditEvents);
}
@ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria")
public Object getData(String dateAfter, String principal) {
List<AuditEvent> auditEvents = this.auditEventRepository.find(principal,
parseDate(dateAfter));
return convert(auditEvents);
}
@ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria")
public Object getData(String principal, String dateAfter, String type) {
List<AuditEvent> auditEvents = this.auditEventRepository.find(principal,
parseDate(dateAfter), type);
return convert(auditEvents);
}
private Date parseDate(String date) {
try {
if (StringUtils.hasLength(date)) {
return new SimpleDateFormat(DATE_FORMAT).parse(date);
}
return null;
}
catch (ParseException ex) {
throw new IllegalArgumentException(ex);
}
}
}

View File

@ -0,0 +1,55 @@
/*
* 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.endpoint.jmx;
import java.util.Date;
import java.util.List;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.jmx.JmxEndpointExtension;
/**
* JMX-specific extension of the {@link AuditEventsEndpoint}.
*
* @author Vedran Pavic
* @author Andy Wilkinson
* @since 2.0.0
*/
@JmxEndpointExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsJmxEndpointExtension {
private final AuditEventsEndpoint delegate;
public AuditEventsJmxEndpointExtension(AuditEventsEndpoint delegate) {
this.delegate = delegate;
}
@ReadOperation
public List<AuditEvent> eventsWithDateAfter(Date dateAfter) {
return this.delegate.eventsWithPrincipalDateAfterAndType(null, dateAfter, null);
}
@ReadOperation
public List<AuditEvent> eventsWithPrincipalAndDateAfter(String principal,
Date dateAfter) {
return this.delegate.eventsWithPrincipalDateAfterAndType(principal, dateAfter,
null);
}
}

View File

@ -1,62 +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.endpoint.jmx;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Internal converter that uses an {@link ObjectMapper} to convert to JSON.
*
* @author Christian Dupuis
* @author Andy Wilkinson
* @author Phillip Webb
*/
class DataConverter {
private final ObjectMapper objectMapper;
private final JavaType listObject;
private final JavaType mapStringObject;
DataConverter(ObjectMapper objectMapper) {
this.objectMapper = (objectMapper == null ? new ObjectMapper() : objectMapper);
this.listObject = this.objectMapper.getTypeFactory()
.constructParametricType(List.class, Object.class);
this.mapStringObject = this.objectMapper.getTypeFactory()
.constructParametricType(Map.class, String.class, Object.class);
}
public Object convert(Object data) {
if (data == null) {
return null;
}
if (data instanceof String) {
return data;
}
if (data.getClass().isArray() || data instanceof List) {
return this.objectMapper.convertValue(data, this.listObject);
}
return this.objectMapper.convertValue(data, this.mapStringObject);
}
}

View File

@ -1,49 +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.endpoint.jmx;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.jmx.export.annotation.ManagedAttribute;
/**
* Simple wrapper around {@link Endpoint} implementations that provide actuator data of
* some sort.
*
* @author Christian Dupuis
* @author Andy Wilkinson
*/
public class DataEndpointMBean extends EndpointMBean {
/**
* Create a new {@link DataEndpointMBean} instance.
* @param beanName the bean name
* @param endpoint the endpoint to wrap
* @param objectMapper the {@link ObjectMapper} used to convert the payload
*/
public DataEndpointMBean(String beanName, Endpoint<?> endpoint,
ObjectMapper objectMapper) {
super(beanName, endpoint, objectMapper);
}
@ManagedAttribute(description = "Invoke the underlying endpoint")
public Object getData() {
return convert(getEndpoint().invoke());
}
}

View File

@ -1,91 +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.endpoint.jmx;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Base for adapters that convert {@link Endpoint} implementations to {@link JmxEndpoint}.
*
* @author Christian Dupuis
* @author Andy Wilkinson
* @author Vedran Pavic
* @author Phillip Webb
* @see JmxEndpoint
* @see DataEndpointMBean
*/
public abstract class EndpointMBean implements JmxEndpoint {
private final DataConverter dataConverter;
private final Endpoint<?> endpoint;
/**
* Create a new {@link EndpointMBean} instance.
* @param beanName the bean name
* @param endpoint the endpoint to wrap
* @param objectMapper the {@link ObjectMapper} used to convert the payload
*/
public EndpointMBean(String beanName, Endpoint<?> endpoint,
ObjectMapper objectMapper) {
this.dataConverter = new DataConverter(objectMapper);
Assert.notNull(beanName, "BeanName must not be null");
Assert.notNull(endpoint, "Endpoint must not be null");
this.endpoint = endpoint;
}
@ManagedAttribute(description = "Returns the class of the underlying endpoint")
public String getEndpointClass() {
return ClassUtils.getQualifiedName(getEndpointType());
}
@Override
public boolean isEnabled() {
return this.endpoint.isEnabled();
}
@Override
public String getIdentity() {
return ObjectUtils.getIdentityHexString(getEndpoint());
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {
return getEndpoint().getClass();
}
public Endpoint<?> getEndpoint() {
return this.endpoint;
}
/**
* Convert the given data into JSON.
* @param data the source data
* @return the JSON representation
*/
protected Object convert(Object data) {
return this.dataConverter.convert(data);
}
}

View File

@ -1,374 +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.endpoint.jmx;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.jmx.export.MBeanExportException;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
import org.springframework.jmx.export.metadata.InvalidMetadataException;
import org.springframework.jmx.export.metadata.JmxAttributeSource;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.export.naming.SelfNaming;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link SmartLifecycle} bean that registers all known {@link Endpoint}s with an
* {@link MBeanServer} using the {@link MBeanExporter} located from the application
* context.
*
* @author Christian Dupuis
* @author Andy Wilkinson
* @author Vedran Pavic
*/
public class EndpointMBeanExporter extends MBeanExporter
implements SmartLifecycle, ApplicationContextAware {
/**
* The default JMX domain.
*/
public static final String DEFAULT_DOMAIN = "org.springframework.boot";
private static final Log logger = LogFactory.getLog(EndpointMBeanExporter.class);
private final AnnotationJmxAttributeSource attributeSource = new EndpointJmxAttributeSource();
private final MetadataMBeanInfoAssembler assembler = new MetadataMBeanInfoAssembler(
this.attributeSource);
private final MetadataNamingStrategy defaultNamingStrategy = new MetadataNamingStrategy(
this.attributeSource);
private final Set<Class<?>> registeredEndpoints = new HashSet<>();
private volatile boolean autoStartup = true;
private volatile int phase = 0;
private volatile boolean running = false;
private final ReentrantLock lifecycleLock = new ReentrantLock();
private ApplicationContext applicationContext;
private ListableBeanFactory beanFactory;
private String domain = DEFAULT_DOMAIN;
private boolean ensureUniqueRuntimeObjectNames = false;
private Properties objectNameStaticProperties = new Properties();
private final ObjectMapper objectMapper;
/**
* Create a new {@link EndpointMBeanExporter} instance.
*/
public EndpointMBeanExporter() {
this(null);
}
/**
* Create a new {@link EndpointMBeanExporter} instance.
* @param objectMapper the object mapper
*/
public EndpointMBeanExporter(ObjectMapper objectMapper) {
this.objectMapper = (objectMapper == null ? new ObjectMapper() : objectMapper);
setAutodetect(false);
setNamingStrategy(this.defaultNamingStrategy);
setAssembler(this.assembler);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
if (beanFactory instanceof ListableBeanFactory) {
this.beanFactory = (ListableBeanFactory) beanFactory;
}
else {
logger.warn("EndpointMBeanExporter not running in a ListableBeanFactory: "
+ "autodetection of Endpoints not available.");
}
}
public void setDomain(String domain) {
this.domain = domain;
}
@Override
public void setEnsureUniqueRuntimeObjectNames(
boolean ensureUniqueRuntimeObjectNames) {
super.setEnsureUniqueRuntimeObjectNames(ensureUniqueRuntimeObjectNames);
this.ensureUniqueRuntimeObjectNames = ensureUniqueRuntimeObjectNames;
}
public void setObjectNameStaticProperties(Properties objectNameStaticProperties) {
this.objectNameStaticProperties = objectNameStaticProperties;
}
protected void doStart() {
locateAndRegisterEndpoints();
}
protected void locateAndRegisterEndpoints() {
registerJmxEndpoints(this.beanFactory.getBeansOfType(JmxEndpoint.class));
registerEndpoints(this.beanFactory.getBeansOfType(Endpoint.class));
}
private void registerJmxEndpoints(Map<String, JmxEndpoint> endpoints) {
for (Map.Entry<String, JmxEndpoint> entry : endpoints.entrySet()) {
String name = entry.getKey();
JmxEndpoint endpoint = entry.getValue();
Class<?> type = (endpoint.getEndpointType() != null
? endpoint.getEndpointType() : endpoint.getClass());
if (!this.registeredEndpoints.contains(type) && endpoint.isEnabled()) {
try {
registerBeanNameOrInstance(endpoint, name);
}
catch (MBeanExportException ex) {
logger.error("Could not register JmxEndpoint [" + name + "]", ex);
}
this.registeredEndpoints.add(type);
}
}
}
@SuppressWarnings("rawtypes")
private void registerEndpoints(Map<String, Endpoint> endpoints) {
for (Map.Entry<String, Endpoint> entry : endpoints.entrySet()) {
String name = entry.getKey();
Endpoint endpoint = entry.getValue();
Class<?> type = endpoint.getClass();
if (!this.registeredEndpoints.contains(type) && endpoint.isEnabled()) {
registerEndpoint(name, endpoint);
this.registeredEndpoints.add(type);
}
}
}
/**
* Register a regular {@link Endpoint} with the {@link MBeanServer}.
* @param beanName the bean name
* @param endpoint the endpoint to register
* @deprecated as of 1.5 in favor of direct {@link JmxEndpoint} registration or
* {@link #adaptEndpoint(String, Endpoint)}
*/
@Deprecated
protected void registerEndpoint(String beanName, Endpoint<?> endpoint) {
Class<?> type = endpoint.getClass();
if (isAnnotatedWithManagedResource(type) || (type.isMemberClass()
&& isAnnotatedWithManagedResource(type.getEnclosingClass()))) {
// Endpoint is directly managed
return;
}
JmxEndpoint jmxEndpoint = adaptEndpoint(beanName, endpoint);
try {
registerBeanNameOrInstance(jmxEndpoint, beanName);
}
catch (MBeanExportException ex) {
logger.error("Could not register MBean for endpoint [" + beanName + "]", ex);
}
}
private boolean isAnnotatedWithManagedResource(Class<?> type) {
return AnnotationUtils.findAnnotation(type, ManagedResource.class) != null;
}
/**
* Adapt the given {@link Endpoint} to a {@link JmxEndpoint}.
* @param beanName the bean name
* @param endpoint the endpoint to adapt
* @return an adapted endpoint
*/
protected JmxEndpoint adaptEndpoint(String beanName, Endpoint<?> endpoint) {
if (endpoint instanceof ShutdownEndpoint) {
return new ShutdownEndpointMBean(beanName, endpoint, this.objectMapper);
}
if (endpoint instanceof LoggersEndpoint) {
return new LoggersEndpointMBean(beanName, endpoint, this.objectMapper);
}
return new DataEndpointMBean(beanName, endpoint, this.objectMapper);
}
@Override
protected ObjectName getObjectName(Object bean, String beanKey)
throws MalformedObjectNameException {
if (bean instanceof SelfNaming) {
return ((SelfNaming) bean).getObjectName();
}
if (bean instanceof JmxEndpoint) {
return getObjectName((JmxEndpoint) bean, beanKey);
}
return this.defaultNamingStrategy.getObjectName(bean, beanKey);
}
private ObjectName getObjectName(JmxEndpoint jmxEndpoint, String beanKey)
throws MalformedObjectNameException {
StringBuilder builder = new StringBuilder();
builder.append(this.domain);
builder.append(":type=Endpoint");
builder.append(",name=" + beanKey);
if (parentContextContainsSameBean(this.applicationContext, beanKey)) {
builder.append(",context="
+ ObjectUtils.getIdentityHexString(this.applicationContext));
}
if (this.ensureUniqueRuntimeObjectNames) {
builder.append(",identity=" + jmxEndpoint.getIdentity());
}
builder.append(getStaticNames());
return ObjectNameManager.getInstance(builder.toString());
}
private boolean parentContextContainsSameBean(ApplicationContext applicationContext,
String beanKey) {
if (applicationContext.getParent() != null) {
try {
Object bean = this.applicationContext.getParent().getBean(beanKey);
if (bean instanceof Endpoint || bean instanceof JmxEndpoint) {
return true;
}
}
catch (BeansException ex) {
// Ignore and continue
}
return parentContextContainsSameBean(applicationContext.getParent(), beanKey);
}
return false;
}
private String getStaticNames() {
if (this.objectNameStaticProperties.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder();
for (Entry<Object, Object> name : this.objectNameStaticProperties.entrySet()) {
builder.append("," + name.getKey() + "=" + name.getValue());
}
return builder.toString();
}
@Override
public final int getPhase() {
return this.phase;
}
@Override
public final boolean isAutoStartup() {
return this.autoStartup;
}
@Override
public final boolean isRunning() {
this.lifecycleLock.lock();
try {
return this.running;
}
finally {
this.lifecycleLock.unlock();
}
}
@Override
public final void start() {
this.lifecycleLock.lock();
try {
if (!this.running) {
this.doStart();
this.running = true;
}
}
finally {
this.lifecycleLock.unlock();
}
}
@Override
public final void stop() {
this.lifecycleLock.lock();
try {
if (this.running) {
this.running = false;
}
}
finally {
this.lifecycleLock.unlock();
}
}
@Override
public final void stop(Runnable callback) {
this.lifecycleLock.lock();
try {
this.stop();
callback.run();
}
finally {
this.lifecycleLock.unlock();
}
}
/**
* {@link JmxAttributeSource} for {@link JmxEndpoint JmxEndpoints}.
*/
private static class EndpointJmxAttributeSource extends AnnotationJmxAttributeSource {
@Override
public org.springframework.jmx.export.metadata.ManagedResource getManagedResource(
Class<?> beanClass) throws InvalidMetadataException {
Assert.state(super.getManagedResource(beanClass) == null,
"@ManagedResource annotation found on JmxEndpoint " + beanClass);
return new org.springframework.jmx.export.metadata.ManagedResource();
}
}
}

View File

@ -1,56 +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.endpoint.jmx;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.jmx.export.annotation.ManagedResource;
/**
* A strategy for the JMX layer on top of an {@link Endpoint}. Implementations are allowed
* to use {@code @ManagedAttribute} and the full Spring JMX machinery but should not use
* the {@link ManagedResource @ManagedResource} annotation. Implementations may be backed
* by an actual {@link Endpoint} or may be specifically designed for JMX only.
*
* @author Phillip Webb
* @since 1.5.0
* @see EndpointMBean
* @see AbstractJmxEndpoint
*/
public interface JmxEndpoint {
/**
* Return if the JMX endpoint is enabled.
* @return if the endpoint is enabled
*/
boolean isEnabled();
/**
* Return the MBean identity for this endpoint.
* @return the MBean identity.
*/
String getIdentity();
/**
* Return the type of {@link Endpoint} exposed, or {@code null} if this
* {@link JmxEndpoint} exposes information that cannot be represented as a traditional
* {@link Endpoint}.
* @return the endpoint type
*/
@SuppressWarnings("rawtypes")
Class<? extends Endpoint> getEndpointType();
}

View File

@ -1,77 +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.endpoint.jmx;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.logging.LogLevel;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.util.StringUtils;
/**
* Adapter to expose {@link LoggersEndpoint} as an {@link MvcEndpoint}.
*
* @author Vedran Pavic
* @author Stephane Nicoll
* @since 1.5.0
*/
public class LoggersEndpointMBean extends EndpointMBean {
public LoggersEndpointMBean(String beanName, Endpoint<?> endpoint,
ObjectMapper objectMapper) {
super(beanName, endpoint, objectMapper);
}
@ManagedAttribute(description = "Get log levels for all known loggers")
public Object getLoggers() {
return convert(getEndpoint().invoke());
}
@ManagedOperation(description = "Get log level for a given logger")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "loggerName", description = "Name of the logger") })
public Object getLogger(String loggerName) {
return convert(getEndpoint().invoke(loggerName));
}
@ManagedOperation(description = "Set log level for a given logger")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "loggerName", description = "Name of the logger"),
@ManagedOperationParameter(name = "logLevel", description = "New log level (can be null or empty String to remove the custom level)") })
public void setLogLevel(String loggerName, String logLevel) {
getEndpoint().setLogLevel(loggerName, determineLogLevel(logLevel));
}
private LogLevel determineLogLevel(String logLevel) {
if (StringUtils.hasText(logLevel)) {
return LogLevel.valueOf(logLevel.toUpperCase());
}
return null;
}
@Override
public LoggersEndpoint getEndpoint() {
return (LoggersEndpoint) super.getEndpoint();
}
}

View File

@ -1,49 +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.endpoint.jmx;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.jmx.export.annotation.ManagedOperation;
/**
* Special endpoint wrapper for {@link ShutdownEndpoint}.
*
* @author Christian Dupuis
* @author Andy Wilkinson
*/
public class ShutdownEndpointMBean extends EndpointMBean {
/**
* Create a new {@link ShutdownEndpointMBean} instance.
* @param beanName the bean name
* @param endpoint the endpoint to wrap
* @param objectMapper the {@link ObjectMapper} used to convert the payload
*/
public ShutdownEndpointMBean(String beanName, Endpoint<?> endpoint,
ObjectMapper objectMapper) {
super(beanName, endpoint, objectMapper);
}
@ManagedOperation(description = "Shutdown the ApplicationContext")
public Object shutdown() {
return convert(getEndpoint().invoke());
}
}

View File

@ -1,309 +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.endpoint.mvc;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
* {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getId()}.
* The semantics of {@code @RequestMapping} should be identical to a normal
* {@code @Controller}, but the endpoints should not be annotated as {@code @Controller}
* (otherwise they will be mapped by the normal MVC mechanisms).
* <p>
* One of the aims of the mapping is to support endpoints that work as HTTP endpoints but
* can still provide useful service interfaces when there is no HTTP server (and no Spring
* MVC on the classpath). Note that any endpoints having method signatures will break in a
* non-servlet environment.
*
* @param <E> The endpoint type
* @author Phillip Webb
* @author Christian Dupuis
* @author Dave Syer
* @author Madhura Bhave
*/
public abstract class AbstractEndpointHandlerMapping<E extends MvcEndpoint>
extends RequestMappingHandlerMapping {
private final Set<E> endpoints;
private HandlerInterceptor securityInterceptor;
private final CorsConfiguration corsConfiguration;
private String prefix = "";
private boolean disabled = false;
/**
* Create a new {@link AbstractEndpointHandlerMapping} instance. All {@link Endpoint}s
* will be detected from the {@link ApplicationContext}. The endpoints will not accept
* CORS requests.
* @param endpoints the endpoints
*/
public AbstractEndpointHandlerMapping(Collection<? extends E> endpoints) {
this(endpoints, null);
}
/**
* Create a new {@link AbstractEndpointHandlerMapping} instance. All {@link Endpoint}s
* will be detected from the {@link ApplicationContext}. The endpoints will accepts
* CORS requests based on the given {@code corsConfiguration}.
* @param endpoints the endpoints
* @param corsConfiguration the CORS configuration for the endpoints
* @since 1.3.0
*/
public AbstractEndpointHandlerMapping(Collection<? extends E> endpoints,
CorsConfiguration corsConfiguration) {
this.endpoints = new HashSet<>(endpoints);
postProcessEndpoints(this.endpoints);
this.corsConfiguration = corsConfiguration;
// By default the static resource handler mapping is LOWEST_PRECEDENCE - 1
// and the RequestMappingHandlerMapping is 0 (we ideally want to be before both)
setOrder(-100);
setUseSuffixPatternMatch(false);
}
/**
* Post process the endpoint setting before they are used. Subclasses can add or
* modify the endpoints as necessary.
* @param endpoints the endpoints to post process
*/
protected void postProcessEndpoints(Set<E> endpoints) {
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (!this.disabled) {
for (MvcEndpoint endpoint : this.endpoints) {
detectHandlerMethods(endpoint);
}
}
}
/**
* Since all handler beans are passed into the constructor there is no need to detect
* anything here.
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return false;
}
@Override
@Deprecated
protected void registerHandlerMethod(Object handler, Method method,
RequestMappingInfo mapping) {
if (mapping == null) {
return;
}
String[] patterns = getPatterns(handler, mapping);
if (!ObjectUtils.isEmpty(patterns)) {
super.registerHandlerMethod(handler, method,
withNewPatterns(mapping, patterns));
}
}
private String[] getPatterns(Object handler, RequestMappingInfo mapping) {
if (handler instanceof String) {
handler = getApplicationContext().getBean((String) handler);
}
Assert.state(handler instanceof MvcEndpoint, "Only MvcEndpoints are supported");
String path = getPath((MvcEndpoint) handler);
return (path == null ? null : getEndpointPatterns(path, mapping));
}
/**
* Return the path that should be used to map the given {@link MvcEndpoint}.
* @param endpoint the endpoint to map
* @return the path to use for the endpoint or {@code null} if no mapping is required
*/
protected String getPath(MvcEndpoint endpoint) {
return endpoint.getPath();
}
private String[] getEndpointPatterns(String path, RequestMappingInfo mapping) {
String patternPrefix = StringUtils.hasText(this.prefix) ? this.prefix + path
: path;
Set<String> defaultPatterns = mapping.getPatternsCondition().getPatterns();
if (defaultPatterns.isEmpty()) {
return new String[] { patternPrefix, patternPrefix + ".json" };
}
List<String> patterns = new ArrayList<>(defaultPatterns);
for (int i = 0; i < patterns.size(); i++) {
patterns.set(i, patternPrefix + patterns.get(i));
}
return patterns.toArray(new String[patterns.size()]);
}
private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping,
String[] patternStrings) {
PatternsRequestCondition patterns = new PatternsRequestCondition(patternStrings,
null, null, useSuffixPatternMatch(), useTrailingSlashMatch(), null);
return new RequestMappingInfo(patterns, mapping.getMethodsCondition(),
mapping.getParamsCondition(), mapping.getHeadersCondition(),
mapping.getConsumesCondition(), mapping.getProducesCondition(),
mapping.getCustomCondition());
}
@Override
protected HandlerExecutionChain getHandlerExecutionChain(Object handler,
HttpServletRequest request) {
HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request);
if (this.securityInterceptor == null || CorsUtils.isCorsRequest(request)) {
return chain;
}
return addSecurityInterceptor(chain);
}
@Override
protected HandlerExecutionChain getCorsHandlerExecutionChain(
HttpServletRequest request, HandlerExecutionChain chain,
CorsConfiguration config) {
chain = super.getCorsHandlerExecutionChain(request, chain, config);
if (this.securityInterceptor == null) {
return chain;
}
return addSecurityInterceptor(chain);
}
@Override
protected void extendInterceptors(List<Object> interceptors) {
interceptors.add(new SkipPathExtensionContentNegotiation());
}
private HandlerExecutionChain addSecurityInterceptor(HandlerExecutionChain chain) {
List<HandlerInterceptor> interceptors = new ArrayList<>();
if (chain.getInterceptors() != null) {
interceptors.addAll(Arrays.asList(chain.getInterceptors()));
}
interceptors.add(this.securityInterceptor);
return new HandlerExecutionChain(chain.getHandler(),
interceptors.toArray(new HandlerInterceptor[interceptors.size()]));
}
/**
* Set the handler interceptor that will be used for security.
* @param securityInterceptor the security handler interceptor
*/
public void setSecurityInterceptor(HandlerInterceptor securityInterceptor) {
this.securityInterceptor = securityInterceptor;
}
/**
* Set the prefix used in mappings.
* @param prefix the prefix
*/
public void setPrefix(String prefix) {
Assert.isTrue("".equals(prefix) || StringUtils.startsWithIgnoreCase(prefix, "/"),
"prefix must start with '/'");
this.prefix = prefix;
}
/**
* Get the prefix used in mappings.
* @return the prefix
*/
public String getPrefix() {
return this.prefix;
}
/**
* Get the path of the endpoint.
* @param endpoint the endpoint
* @return the path used in mappings
*/
public String getPath(String endpoint) {
return this.prefix + endpoint;
}
/**
* Sets if this mapping is disabled.
* @param disabled if the mapping is disabled
*/
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
/**
* Returns if this mapping is disabled.
* @return {@code true} if the mapping is disabled
*/
public boolean isDisabled() {
return this.disabled;
}
/**
* Return the endpoints.
* @return the endpoints
*/
public Set<E> getEndpoints() {
return Collections.unmodifiableSet(this.endpoints);
}
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method,
RequestMappingInfo mappingInfo) {
return this.corsConfiguration;
}
/**
* {@link HandlerInterceptorAdapter} to ensure that
* {@link PathExtensionContentNegotiationStrategy} is skipped for actuator endpoints.
*/
private static final class SkipPathExtensionContentNegotiation
extends HandlerInterceptorAdapter {
private static final String SKIP_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class
.getName() + ".SKIP";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute(SKIP_ATTRIBUTE, Boolean.TRUE);
return true;
}
}
}

View File

@ -1,99 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
/**
* Abstract base class for {@link MvcEndpoint} implementations.
*
* @param <E> The delegate endpoint
* @author Dave Syer
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.3.0
*/
public abstract class AbstractEndpointMvcAdapter<E extends Endpoint<?>>
implements NamedMvcEndpoint {
private final E delegate;
/**
* Endpoint URL path.
*/
private String path;
/**
* Create a new {@link EndpointMvcAdapter}.
* @param delegate the underlying {@link Endpoint} to adapt.
*/
public AbstractEndpointMvcAdapter(E delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
protected Object invoke() {
if (!this.delegate.isEnabled()) {
// Shouldn't happen - shouldn't be registered when delegate's disabled
return getDisabledResponse();
}
return this.delegate.invoke();
}
public E getDelegate() {
return this.delegate;
}
@Override
public String getName() {
return this.delegate.getId();
}
@Override
public String getPath() {
return (this.path != null ? this.path : "/" + this.delegate.getId());
}
public void setPath(String path) {
while (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
if (!path.startsWith("/")) {
path = "/" + path;
}
this.path = path;
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {
return this.delegate.getClass();
}
/**
* Returns the response that should be returned when the endpoint is disabled.
* @return The response to be returned when the endpoint is disabled
* @since 1.2.4
* @see Endpoint#isEnabled()
*/
protected ResponseEntity<?> getDisabledResponse() {
return MvcEndpoint.DISABLED_RESPONSE;
}
}

View File

@ -1,88 +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.endpoint.mvc;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.EndpointProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Abstract base class for {@link MvcEndpoint} implementations without a backing
* {@link Endpoint}.
*
* @author Phillip Webb
* @author Lari Hotari
* @since 1.4.0
*/
public abstract class AbstractMvcEndpoint
implements MvcEndpoint, WebMvcConfigurer, EnvironmentAware {
private Environment environment;
/**
* Endpoint URL path.
*/
private String path;
/**
* Enable the endpoint.
*/
private Boolean enabled;
public AbstractMvcEndpoint(String path) {
setPath(path);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
protected final Environment getEnvironment() {
return this.environment;
}
@Override
public String getPath() {
return this.path;
}
public void setPath(String path) {
Assert.notNull(path, "Path must not be null");
Assert.isTrue(path.isEmpty() || path.startsWith("/"),
"Path must start with / or be empty");
this.path = path;
}
public boolean isEnabled() {
return EndpointProperties.isEnabled(this.environment, this.enabled);
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {
return null;
}
}

View File

@ -1,45 +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.endpoint.mvc;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.util.Assert;
/**
* Abstract base class for {@link NamedMvcEndpoint} implementations without a backing
* {@link Endpoint}.
*
* @author Madhura Bhave
* @since 1.5.0
*/
public abstract class AbstractNamedMvcEndpoint extends AbstractMvcEndpoint
implements NamedMvcEndpoint {
private final String name;
public AbstractNamedMvcEndpoint(String name, String path) {
super(path);
Assert.hasLength(name, "Name must not be empty");
this.name = name;
}
@Override
public String getName() {
return this.name;
}
}

View File

@ -1,56 +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.endpoint.mvc;
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 org.springframework.core.annotation.AliasFor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Specialized {@link RequestMapping} for {@link RequestMethod#POST POST} requests that
* consume {@code application/json} or
* {@code application/vnd.spring-boot.actuator.v1+json} requests and produce
* {@code application/json} or {@code application/vnd.spring-boot.actuator.v1+json}
* responses.
*
* @author Andy Wilkinson
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST, consumes = {
ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE }, produces = {
ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
@interface ActuatorPostMapping {
/**
* Alias for {@link RequestMapping#value}.
* @return the value
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
}

View File

@ -1,64 +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.endpoint.mvc;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* {@link MvcEndpoint} to expose {@link AuditEvent}s.
*
* @author Vedran Pavic
* @author Phillip Webb
* @since 1.5.0
*/
@ConfigurationProperties(prefix = "endpoints.auditevents")
public class AuditEventsMvcEndpoint extends AbstractNamedMvcEndpoint {
private final AuditEventRepository auditEventRepository;
public AuditEventsMvcEndpoint(AuditEventRepository auditEventRepository) {
super("auditevents", "/auditevents");
Assert.notNull(auditEventRepository, "AuditEventRepository must not be null");
this.auditEventRepository = auditEventRepository;
}
@ActuatorGetMapping
@ResponseBody
public ResponseEntity<?> findByPrincipalAndAfterAndType(
@RequestParam(required = false) String principal,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssZ") Date after,
@RequestParam(required = false) String type) {
if (!isEnabled()) {
return DISABLED_RESPONSE;
}
Map<Object, Object> result = new LinkedHashMap<>();
result.put("events", this.auditEventRepository.find(principal, after, type));
return ResponseEntity.ok(result);
}
}

View File

@ -1,137 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.HandlerMapping;
/**
* {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getId()}.
* The semantics of {@code @RequestMapping} should be identical to a normal
* {@code @Controller}, but the endpoints should not be annotated as {@code @Controller}
* (otherwise they will be mapped by the normal MVC mechanisms).
* <p>
* One of the aims of the mapping is to support endpoints that work as HTTP endpoints but
* can still provide useful service interfaces when there is no HTTP server (and no Spring
* MVC on the classpath). Note that any endpoints having method signatures will break in a
* non-servlet environment.
*
* @author Phillip Webb
* @author Christian Dupuis
* @author Dave Syer
*/
public class EndpointHandlerMapping extends AbstractEndpointHandlerMapping<MvcEndpoint> {
/**
* Create a new {@link EndpointHandlerMapping} instance. All {@link Endpoint}s will be
* detected from the {@link ApplicationContext}. The endpoints will not accept CORS
* requests.
* @param endpoints the endpoints
*/
public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) {
super(endpoints);
}
/**
* Create a new {@link EndpointHandlerMapping} instance. All {@link Endpoint}s will be
* detected from the {@link ApplicationContext}. The endpoints will accepts CORS
* requests based on the given {@code corsConfiguration}.
* @param endpoints the endpoints
* @param corsConfiguration the CORS configuration for the endpoints
* @since 1.3.0
*/
public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints,
CorsConfiguration corsConfiguration) {
super(endpoints, corsConfiguration);
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
detectHandlerMethods(new EndpointLinksMvcEndpoint(
getEndpoints().stream().filter(NamedMvcEndpoint.class::isInstance)
.map(NamedMvcEndpoint.class::cast).collect(Collectors.toSet())));
}
/**
* {@link MvcEndpoint} to provide HAL-formatted links to all the
* {@link NamedMvcEndpoint named endpoints}.
*
* @author Madhura Bhave
* @author Andy Wilkinson
*/
private static final class EndpointLinksMvcEndpoint extends AbstractMvcEndpoint {
private final Set<NamedMvcEndpoint> endpoints;
private EndpointLinksMvcEndpoint(Set<NamedMvcEndpoint> endpoints) {
super("");
this.endpoints = endpoints;
}
@ResponseBody
@ActuatorGetMapping
public Map<String, Map<String, Link>> links(HttpServletRequest request) {
Map<String, Link> links = new LinkedHashMap<>();
String url = request.getRequestURL().toString();
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
links.put("self", Link.withHref(url));
for (NamedMvcEndpoint endpoint : this.endpoints) {
links.put(endpoint.getName(),
Link.withHref(url + "/" + endpoint.getName()));
}
return Collections.singletonMap("_links", links);
}
}
/**
* Details for a link in the HAL response.
*/
static final class Link {
private final String href;
private Link(String href) {
this.href = href;
}
public String getHref() {
return this.href;
}
static Link withHref(Object href) {
return new Link(href.toString());
}
}
}

View File

@ -1,131 +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.endpoint.mvc;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Adapter to expose {@link EnvironmentEndpoint} as an {@link MvcEndpoint}.
*
* @author Dave Syer
* @author Christian Dupuis
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "endpoints.env")
public class EnvironmentMvcEndpoint extends EndpointMvcAdapter
implements EnvironmentAware {
private Environment environment;
public EnvironmentMvcEndpoint(EnvironmentEndpoint delegate) {
super(delegate);
}
@ActuatorGetMapping("/{name:.*}")
@ResponseBody
public Object value(@PathVariable String name) {
if (!getDelegate().isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
// disabled
return getDisabledResponse();
}
return new NamePatternEnvironmentFilter(this.environment).getResults(name);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* {@link NamePatternFilter} for the Environment source.
*/
private class NamePatternEnvironmentFilter extends NamePatternFilter<Environment> {
NamePatternEnvironmentFilter(Environment source) {
super(source);
}
@Override
protected void getNames(Environment source, NameCallback callback) {
if (source instanceof ConfigurableEnvironment) {
getNames(((ConfigurableEnvironment) source).getPropertySources(),
callback);
}
}
private void getNames(PropertySources propertySources, NameCallback callback) {
for (PropertySource<?> propertySource : propertySources) {
if (propertySource instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> source = (EnumerablePropertySource<?>) propertySource;
for (String name : source.getPropertyNames()) {
callback.addName(name);
}
}
}
}
@Override
protected Object getOptionalValue(Environment source, String name) {
Object result = getValue(name);
if (result != null) {
result = ((EnvironmentEndpoint) getDelegate()).sanitize(name, result);
}
return result;
}
@Override
protected Object getValue(Environment source, String name) {
Object result = getValue(name);
if (result == null) {
throw new NoSuchPropertyException("No such property: " + name);
}
return ((EnvironmentEndpoint) getDelegate()).sanitize(name, result);
}
private Object getValue(String name) {
return ((EnvironmentEndpoint) getDelegate()).getResolver().getProperty(name,
Object.class);
}
}
/**
* Exception thrown when the specified property cannot be found.
*/
@SuppressWarnings("serial")
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such property")
public static class NoSuchPropertyException extends RuntimeException {
public NoSuchPropertyException(String string) {
super(string);
}
}
}

View File

@ -1,240 +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.endpoint.mvc;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Adapter to expose {@link HealthEndpoint} as an {@link MvcEndpoint}.
*
* @author Christian Dupuis
* @author Dave Syer
* @author Andy Wilkinson
* @author Phillip Webb
* @author Eddú Meléndez
* @author Madhura Bhave
* @since 1.1.0
*/
@ConfigurationProperties(prefix = "endpoints.health")
public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint> {
private static final List<String> DEFAULT_ROLES = Arrays.asList("ROLE_ACTUATOR");
private final boolean secure;
private final List<String> roles;
private Map<String, HttpStatus> statusMapping = new HashMap<>();
private volatile CachedHealth cachedHealth;
public HealthMvcEndpoint(HealthEndpoint delegate) {
this(delegate, true);
}
public HealthMvcEndpoint(HealthEndpoint delegate, boolean secure) {
this(delegate, secure, new ArrayList<>(DEFAULT_ROLES));
}
public HealthMvcEndpoint(HealthEndpoint delegate, boolean secure,
List<String> roles) {
super(delegate);
Assert.notNull(roles, "Roles must not be null");
this.secure = secure;
this.roles = roles;
setupDefaultStatusMapping();
}
private void setupDefaultStatusMapping() {
addStatusMapping(Status.DOWN, HttpStatus.SERVICE_UNAVAILABLE);
addStatusMapping(Status.OUT_OF_SERVICE, HttpStatus.SERVICE_UNAVAILABLE);
}
/**
* Set specific status mappings.
* @param statusMapping a map of status code to {@link HttpStatus}
*/
public void setStatusMapping(Map<String, HttpStatus> statusMapping) {
Assert.notNull(statusMapping, "StatusMapping must not be null");
this.statusMapping = new HashMap<>(statusMapping);
}
/**
* Add specific status mappings to the existing set.
* @param statusMapping a map of status code to {@link HttpStatus}
*/
public void addStatusMapping(Map<String, HttpStatus> statusMapping) {
Assert.notNull(statusMapping, "StatusMapping must not be null");
this.statusMapping.putAll(statusMapping);
}
/**
* Add a status mapping to the existing set.
* @param status the status to map
* @param httpStatus the http status
*/
public void addStatusMapping(Status status, HttpStatus httpStatus) {
Assert.notNull(status, "Status must not be null");
Assert.notNull(httpStatus, "HttpStatus must not be null");
addStatusMapping(status.getCode(), httpStatus);
}
/**
* Add a status mapping to the existing set.
* @param statusCode the status code to map
* @param httpStatus the http status
*/
public void addStatusMapping(String statusCode, HttpStatus httpStatus) {
Assert.notNull(statusCode, "StatusCode must not be null");
Assert.notNull(httpStatus, "HttpStatus must not be null");
this.statusMapping.put(statusCode, httpStatus);
}
@ActuatorGetMapping
@ResponseBody
public Object invoke(HttpServletRequest request, Principal principal) {
if (!getDelegate().isEnabled()) {
// Shouldn't happen because the request mapping should not be registered
return getDisabledResponse();
}
Health health = getHealth(request, principal);
HttpStatus status = getStatus(health);
if (status != null) {
return new ResponseEntity<>(health, status);
}
return health;
}
private HttpStatus getStatus(Health health) {
String code = getUniformValue(health.getStatus().getCode());
if (code != null) {
return this.statusMapping.keySet().stream()
.filter((key) -> code.equals(getUniformValue(key)))
.map(this.statusMapping::get).findFirst().orElse(null);
}
return null;
}
private String getUniformValue(String code) {
if (code == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (char ch : code.toCharArray()) {
if (Character.isAlphabetic(ch) || Character.isDigit(ch)) {
builder.append(Character.toLowerCase(ch));
}
}
return builder.toString();
}
private Health getHealth(HttpServletRequest request, Principal principal) {
Health currentHealth = getCurrentHealth();
if (exposeHealthDetails(request, principal)) {
return currentHealth;
}
return Health.status(currentHealth.getStatus()).build();
}
private Health getCurrentHealth() {
long accessTime = System.currentTimeMillis();
CachedHealth cached = this.cachedHealth;
if (cached == null || cached.isStale(accessTime, getDelegate().getTimeToLive())) {
Health health = getDelegate().invoke();
this.cachedHealth = new CachedHealth(health, accessTime);
return health;
}
return cached.getHealth();
}
protected boolean exposeHealthDetails(HttpServletRequest request,
Principal principal) {
if (!this.secure) {
return true;
}
List<String> roles = getRoles();
for (String role : roles) {
if (request.isUserInRole(role)) {
return true;
}
if (isSpringSecurityAuthentication(principal)) {
Authentication authentication = (Authentication) principal;
for (GrantedAuthority authority : authentication.getAuthorities()) {
String name = authority.getAuthority();
if (role.equals(name)) {
return true;
}
}
}
}
return false;
}
private List<String> getRoles() {
return this.roles;
}
private boolean isSpringSecurityAuthentication(Principal principal) {
return ClassUtils.isPresent("org.springframework.security.core.Authentication",
null) && principal instanceof Authentication;
}
/**
* A cached {@link Health} that encapsulates the {@code Health} itself and the time at
* which it was created.
*/
static class CachedHealth {
private final Health health;
private final long creationTime;
CachedHealth(Health health, long creationTime) {
this.health = health;
this.creationTime = creationTime;
}
public boolean isStale(long accessTime, long timeToLive) {
return (accessTime - this.creationTime) >= timeToLive;
}
public Health getHealth() {
return this.health;
}
}
}

View File

@ -1,138 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
import java.io.File;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.logging.LogFile;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* Controller that provides an API for logfiles, i.e. downloading the main logfile
* configured in environment property 'logging.file' that is standard, but optional
* property for spring-boot applications.
*
* @author Johannes Edmeier
* @author Phillip Webb
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "endpoints.logfile")
public class LogFileMvcEndpoint extends AbstractNamedMvcEndpoint {
private static final Log logger = LogFactory.getLog(LogFileMvcEndpoint.class);
/**
* External Logfile to be accessed. Can be used if the logfile is written by output
* redirect and not by the logging-system itself.
*/
private File externalFile;
public LogFileMvcEndpoint() {
super("logfile", "/logfile");
}
public File getExternalFile() {
return this.externalFile;
}
public void setExternalFile(File externalFile) {
this.externalFile = externalFile;
}
@RequestMapping(method = { RequestMethod.GET, RequestMethod.HEAD })
public void invoke(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (!isEnabled()) {
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
Resource resource = getLogFileResource();
if (resource != null && !resource.exists()) {
if (logger.isDebugEnabled()) {
logger.debug("Log file '" + resource + "' does not exist");
}
resource = null;
}
Handler handler = new Handler(resource, request.getServletContext());
handler.handleRequest(request, response);
}
private Resource getLogFileResource() {
if (this.externalFile != null) {
return new FileSystemResource(this.externalFile);
}
LogFile logFile = LogFile.get(getEnvironment());
if (logFile == null) {
logger.debug("Missing 'logging.file' or 'logging.path' properties");
return null;
}
return new FileSystemResource(logFile.toString());
}
/**
* {@link ResourceHttpRequestHandler} to send the log file.
*/
private static class Handler extends ResourceHttpRequestHandler {
private final Resource resource;
Handler(Resource resource, ServletContext servletContext) {
this.resource = resource;
getLocations().add(resource);
try {
setServletContext(servletContext);
afterPropertiesSet();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
protected void initAllowedLocations() {
this.getLocations().clear();
}
@Override
protected Resource getResource(HttpServletRequest request) throws IOException {
return this.resource;
}
@Override
protected MediaType getMediaType(HttpServletRequest request, Resource resource) {
return MediaType.TEXT_PLAIN;
}
}
}

View File

@ -1,84 +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.endpoint.mvc;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.logging.LogLevel;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Adapter to expose {@link LoggersEndpoint} as an {@link MvcEndpoint}.
*
* @author Ben Hale
* @author Kazuki Shimizu
* @author Eddú Meléndez
* @since 1.5.0
*/
@ConfigurationProperties(prefix = "endpoints.loggers")
public class LoggersMvcEndpoint extends EndpointMvcAdapter {
private final LoggersEndpoint delegate;
public LoggersMvcEndpoint(LoggersEndpoint delegate) {
super(delegate);
this.delegate = delegate;
}
@ActuatorGetMapping("/{name:.*}")
@ResponseBody
public Object get(@PathVariable String name) {
if (!this.delegate.isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
// disabled
return getDisabledResponse();
}
LoggerLevels levels = this.delegate.invoke(name);
return (levels == null ? ResponseEntity.notFound().build() : levels);
}
@ActuatorPostMapping("/{name:.*}")
@ResponseBody
public Object set(@PathVariable String name,
@RequestBody Map<String, String> configuration) {
if (!this.delegate.isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
// disabled
return getDisabledResponse();
}
try {
LogLevel logLevel = getLogLevel(configuration);
this.delegate.setLogLevel(name, logLevel);
return ResponseEntity.noContent().build();
}
catch (IllegalArgumentException ex) {
return ResponseEntity.badRequest().build();
}
}
private LogLevel getLogLevel(Map<String, String> configuration) {
String level = configuration.get("configuredLevel");
return (level == null ? null : LogLevel.valueOf(level.toUpperCase()));
}
}

View File

@ -27,9 +27,9 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
/**
* Special {@link MvcEndpoint} for handling "/error" path when the management servlet is
* in a child context. The regular {@link ErrorController} should be available there but
* because of the way the handler mappings are set up it will not be detected.
* {@link Controller} for handling "/error" path when the management servlet is in a child
* context. The regular {@link ErrorController} should be available there but because of
* the way the handler mappings are set up it will not be detected.
*
* @author Dave Syer
*/

View File

@ -1,106 +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.endpoint.mvc;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Adapter to expose {@link MetricsEndpoint} as an {@link MvcEndpoint}.
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Sergei Egorov
*/
@ConfigurationProperties(prefix = "endpoints.metrics")
public class MetricsMvcEndpoint extends EndpointMvcAdapter {
private final MetricsEndpoint delegate;
public MetricsMvcEndpoint(MetricsEndpoint delegate) {
super(delegate);
this.delegate = delegate;
}
@ActuatorGetMapping("/{name:.*}")
@ResponseBody
public Object value(@PathVariable String name) {
if (!this.delegate.isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
// disabled
return getDisabledResponse();
}
return new NamePatternMapFilter(this.delegate.invoke()).getResults(name);
}
/**
* {@link NamePatternFilter} for the Map source.
*/
private class NamePatternMapFilter extends NamePatternFilter<Map<String, ?>> {
NamePatternMapFilter(Map<String, ?> source) {
super(source);
}
@Override
protected void getNames(Map<String, ?> source, NameCallback callback) {
for (String name : source.keySet()) {
try {
callback.addName(name);
}
catch (NoSuchMetricException ex) {
// Metric with null value. Continue.
}
}
}
@Override
protected Object getOptionalValue(Map<String, ?> source, String name) {
return source.get(name);
}
@Override
protected Object getValue(Map<String, ?> source, String name) {
Object value = getOptionalValue(source, name);
if (value == null) {
throw new NoSuchMetricException("No such metric: " + name);
}
return value;
}
}
/**
* Exception thrown when the specified metric cannot be found.
*/
@SuppressWarnings("serial")
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such metric")
public static class NoSuchMetricException extends RuntimeException {
public NoSuchMetricException(String string) {
super(string);
}
}
}

View File

@ -1,60 +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.endpoint.mvc;
import java.util.Collections;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/**
* A strategy for the MVC layer on top of an {@link Endpoint}. Implementations are allowed
* to use {@code @RequestMapping} and the full Spring MVC machinery, but should not use
* {@code @Controller} or {@code @RequestMapping} at the type level (since that would lead
* to a double mapping of paths, once by the regular MVC handler mappings and once by the
* {@link EndpointHandlerMapping}).
*
* @author Dave Syer
* @see NamedMvcEndpoint
*/
public interface MvcEndpoint {
/**
* A {@link ResponseEntity} returned for disabled endpoints.
*/
ResponseEntity<Map<String, String>> DISABLED_RESPONSE = new ResponseEntity<>(
Collections.singletonMap("message", "This endpoint is disabled"),
HttpStatus.NOT_FOUND);
/**
* Return the MVC path of the endpoint.
* @return the endpoint path
*/
String getPath();
/**
* Return the type of {@link Endpoint} exposed, or {@code null} if this
* {@link MvcEndpoint} exposes information that cannot be represented as a traditional
* {@link Endpoint}.
* @return the endpoint type
*/
@SuppressWarnings("rawtypes")
Class<? extends Endpoint> getEndpointType();
}

View File

@ -1,143 +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.endpoint.mvc;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
* Security interceptor for MvcEndpoints.
*
* @author Madhura Bhave
* @since 1.5.0
*/
public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter {
private static final Log logger = LogFactory
.getLog(MvcEndpointSecurityInterceptor.class);
private final boolean secure;
private final List<String> roles;
private AtomicBoolean loggedUnauthorizedAttempt = new AtomicBoolean();
public MvcEndpointSecurityInterceptor(boolean secure, List<String> roles) {
this.secure = secure;
this.roles = roles;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (CorsUtils.isPreFlightRequest(request) || !this.secure) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
if (HttpMethod.OPTIONS.matches(request.getMethod())
&& !(handlerMethod.getBean() instanceof MvcEndpoint)) {
return true;
}
if (isUserAllowedAccess(request)) {
return true;
}
sendFailureResponse(request, response);
return false;
}
private boolean isUserAllowedAccess(HttpServletRequest request) {
AuthoritiesValidator authoritiesValidator = null;
if (isSpringSecurityAvailable()) {
authoritiesValidator = new AuthoritiesValidator();
}
for (String role : this.roles) {
if (request.isUserInRole(role)) {
return true;
}
if (authoritiesValidator != null && authoritiesValidator.hasAuthority(role)) {
return true;
}
}
return false;
}
private boolean isSpringSecurityAvailable() {
return ClassUtils.isPresent(
"org.springframework.security.config.annotation.web.WebSecurityConfigurer",
getClass().getClassLoader());
}
private void sendFailureResponse(HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (request.getUserPrincipal() != null) {
String roles = StringUtils.collectionToDelimitedString(this.roles, " ");
response.sendError(HttpStatus.FORBIDDEN.value(),
"Access is denied. User must have one of the these roles: " + roles);
}
else {
logUnauthorizedAttempt();
response.sendError(HttpStatus.UNAUTHORIZED.value(),
"Full authentication is required to access this resource.");
}
}
private void logUnauthorizedAttempt() {
if (this.loggedUnauthorizedAttempt.compareAndSet(false, true)
&& logger.isInfoEnabled()) {
logger.info("Full authentication is required to access "
+ "actuator endpoints. Consider adding Spring Security "
+ "or set 'management.security.enabled' to false.");
}
}
/**
* Inner class to check authorities using Spring Security (when available).
*/
private static class AuthoritiesValidator {
private boolean hasAuthority(String role) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication != null) {
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (authority.getAuthority().equals(role)) {
return true;
}
}
}
return false;
}
}
}

View File

@ -1,125 +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.endpoint.mvc;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
/**
* A registry for all {@link MvcEndpoint} beans, and a factory for a set of generic ones
* wrapping existing {@link Endpoint} instances that are not already exposed as MVC
* endpoints.
*
* @author Dave Syer
*/
public class MvcEndpoints implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
private final Set<MvcEndpoint> endpoints = new HashSet<>();
private Set<Class<?>> customTypes;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
Collection<MvcEndpoint> existing = BeanFactoryUtils
.beansOfTypeIncludingAncestors(this.applicationContext, MvcEndpoint.class)
.values();
this.endpoints.addAll(existing);
this.customTypes = findEndpointClasses(existing);
@SuppressWarnings("rawtypes")
Collection<Endpoint> delegates = BeanFactoryUtils
.beansOfTypeIncludingAncestors(this.applicationContext, Endpoint.class)
.values();
for (Endpoint<?> endpoint : delegates) {
if (isGenericEndpoint(endpoint.getClass()) && endpoint.isEnabled()) {
EndpointMvcAdapter adapter = new EndpointMvcAdapter(endpoint);
String path = determinePath(endpoint,
this.applicationContext.getEnvironment());
if (path != null) {
adapter.setPath(path);
}
this.endpoints.add(adapter);
}
}
}
private Set<Class<?>> findEndpointClasses(Collection<MvcEndpoint> existing) {
Set<Class<?>> types = new HashSet<>();
for (MvcEndpoint endpoint : existing) {
Class<?> type = endpoint.getEndpointType();
if (type != null) {
types.add(type);
}
}
return types;
}
public Set<MvcEndpoint> getEndpoints() {
return this.endpoints;
}
/**
* Return the endpoints of the specified type.
* @param <E> the Class type of the endpoints to be returned
* @param type the endpoint type
* @return the endpoints
*/
@SuppressWarnings("unchecked")
public <E extends MvcEndpoint> Set<E> getEndpoints(Class<E> type) {
Set<E> result = new HashSet<>(this.endpoints.size());
for (MvcEndpoint candidate : this.endpoints) {
if (type.isInstance(candidate)) {
result.add((E) candidate);
}
}
return Collections.unmodifiableSet(result);
}
private boolean isGenericEndpoint(Class<?> type) {
return !this.customTypes.contains(type)
&& !MvcEndpoint.class.isAssignableFrom(type);
}
private String determinePath(Endpoint<?> endpoint, Environment environment) {
ConfigurationProperties configurationProperties = AnnotationUtils
.findAnnotation(endpoint.getClass(), ConfigurationProperties.class);
if (configurationProperties != null) {
return environment.getProperty(configurationProperties.prefix() + ".path");
}
return null;
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.mvc;
/**
* An {@link MvcEndpoint} that also includes a logical name. Unlike {@link #getPath()
* endpoints paths}, it should not be possible for a user to change the endpoint name.
* Names provide a consistent way to reference an endpoint, for example they may be used
* as the {@literal 'rel'} attribute in a HAL response.
*
* @author Madhura Bhave
* @since 1.5.0
*/
public interface NamedMvcEndpoint extends MvcEndpoint {
/**
* Return the logical name of the endpoint. Names should be non-null, non-empty,
* alpha-numeric values.
* @return the logical name of the endpoint
*/
String getName();
}

View File

@ -1,54 +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.endpoint.mvc;
import java.util.Collections;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Adapter to expose {@link ShutdownEndpoint} as an {@link MvcEndpoint}.
*
* @author Dave Syer
*/
@ConfigurationProperties(prefix = "endpoints.shutdown")
public class ShutdownMvcEndpoint extends EndpointMvcAdapter {
public ShutdownMvcEndpoint(ShutdownEndpoint delegate) {
super(delegate);
}
@PostMapping(produces = { ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
@ResponseBody
@Override
public Object invoke() {
if (!getDelegate().isEnabled()) {
return new ResponseEntity<>(
Collections.singletonMap("message", "This endpoint is disabled"),
HttpStatus.NOT_FOUND);
}
return super.invoke();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* 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.
@ -17,6 +17,5 @@
/**
* Endpoints used to expose actuator information.
*
* @see org.springframework.boot.actuate.endpoint.Endpoint
*/
package org.springframework.boot.actuate.endpoint;

View File

@ -0,0 +1,51 @@
/*
* 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.endpoint.web;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.web.WebEndpointExtension;
/**
* Web-specific extension of the {@link AuditEventsEndpoint}.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
@WebEndpointExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsWebEndpointExtension {
private final AuditEventsEndpoint delegate;
public AuditEventsWebEndpointExtension(AuditEventsEndpoint delegate) {
this.delegate = delegate;
}
@ReadOperation
public Map<String, List<AuditEvent>> eventsWithPrincipalDateAfterAndType(
String principal, Date after, String type) {
return Collections.singletonMap("events", this.delegate
.eventsWithPrincipalDateAfterAndType(principal, after, type));
}
}

View File

@ -0,0 +1,129 @@
/*
* 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.endpoint.web;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.web.WebEndpointExtension;
import org.springframework.boot.endpoint.web.WebEndpointResponse;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
/**
* {@link WebEndpointExtension} for the {@link HealthEndpoint}.
*
* @author Christian Dupuis
* @author Dave Syer
* @author Andy Wilkinson
* @author Phillip Webb
* @author Eddú Meléndez
* @author Madhura Bhave
* @since 2.0.0
*/
@WebEndpointExtension(endpoint = HealthEndpoint.class)
public class HealthWebEndpointExtension {
private Map<String, Integer> statusMapping = new HashMap<>();
private final HealthEndpoint delegate;
public HealthWebEndpointExtension(HealthEndpoint delegate) {
this.delegate = delegate;
setupDefaultStatusMapping();
}
private void setupDefaultStatusMapping() {
addStatusMapping(Status.DOWN, 503);
addStatusMapping(Status.OUT_OF_SERVICE, 503);
}
/**
* Set specific status mappings.
* @param statusMapping a map of status code to {@link HttpStatus}
*/
public void setStatusMapping(Map<String, Integer> statusMapping) {
Assert.notNull(statusMapping, "StatusMapping must not be null");
this.statusMapping = new HashMap<>(statusMapping);
}
/**
* Add specific status mappings to the existing set.
* @param statusMapping a map of status code to {@link HttpStatus}
*/
public void addStatusMapping(Map<String, Integer> statusMapping) {
Assert.notNull(statusMapping, "StatusMapping must not be null");
this.statusMapping.putAll(statusMapping);
}
/**
* Add a status mapping to the existing set.
* @param status the status to map
* @param httpStatus the http status
*/
public void addStatusMapping(Status status, Integer httpStatus) {
Assert.notNull(status, "Status must not be null");
Assert.notNull(httpStatus, "HttpStatus must not be null");
addStatusMapping(status.getCode(), httpStatus);
}
/**
* Add a status mapping to the existing set.
* @param statusCode the status code to map
* @param httpStatus the http status
*/
public void addStatusMapping(String statusCode, Integer httpStatus) {
Assert.notNull(statusCode, "StatusCode must not be null");
Assert.notNull(httpStatus, "HttpStatus must not be null");
this.statusMapping.put(statusCode, httpStatus);
}
@ReadOperation
public WebEndpointResponse<Health> getHealth() {
Health health = this.delegate.health();
Integer status = getStatus(health);
return new WebEndpointResponse<Health>(health, status);
}
private int getStatus(Health health) {
String code = getUniformValue(health.getStatus().getCode());
if (code != null) {
return this.statusMapping.keySet().stream()
.filter((key) -> code.equals(getUniformValue(key)))
.map(this.statusMapping::get).findFirst().orElse(200);
}
return 200;
}
private String getUniformValue(String code) {
if (code == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (char ch : code.toCharArray()) {
if (Character.isAlphabetic(ch) || Character.isDigit(ch)) {
builder.append(Character.toLowerCase(ch));
}
}
return builder.toString();
}
}

Some files were not shown because too many files have changed in this diff Show More