diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 6a05a9ffad..12ecfce2d2 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -153,6 +153,16 @@ flyway-core true + + org.glassfish.jersey.core + jersey-server + true + + + org.glassfish.jersey.containers + jersey-container-servlet-core + true + org.hibernate hibernate-validator @@ -183,6 +193,11 @@ spring-jdbc true + + org.springframework + spring-webflux + true + org.springframework spring-webmvc @@ -241,16 +256,6 @@ spring-integration-core true - - org.springframework.hateoas - spring-hateoas - true - - - org.springframework.plugin - spring-plugin-core - true - org.springframework.security spring-security-web @@ -261,11 +266,6 @@ spring-security-config true - - org.webjars - hal-browser - true - org.springframework.boot @@ -298,6 +298,10 @@ json-path test + + io.projectreactor.ipc + reactor-netty + io.undertow undertow-core @@ -324,6 +328,16 @@ hsqldb test + + org.glassfish.jersey.media + jersey-media-json-jackson + test + + + org.glassfish.jersey.ext + jersey-spring3 + test + org.skyscreamer jsonassert diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnManagementPort.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnManagementPort.java new file mode 100644 index 0000000000..c50013fce0 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnManagementPort.java @@ -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(); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableChildManagementContextConfiguration.java similarity index 59% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableChildManagementContextConfiguration.java index 68af109e69..b4f4a39c99 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableChildManagementContextConfiguration.java @@ -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 { } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorGetMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableManagementContext.java similarity index 52% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorGetMapping.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableManagementContext.java index 30e9bbbf8b..807538075a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorGetMapping.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EnableManagementContext.java @@ -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(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextAutoConfiguration.java new file mode 100644 index 0000000000..5ac3d7b986 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextAutoConfiguration.java @@ -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("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 { + + 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)); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfiguration.java index 47fe048150..ce3b3db365 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfiguration.java @@ -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; + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelector.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelector.java similarity index 80% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelector.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelector.java index b529614552..6ce1ae8524 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelector.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelector.java @@ -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 configurations = getConfigurations(); OrderComparator.sort(configurations); List 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 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; + } + } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextFactory.java new file mode 100644 index 0000000000..fe054240d6 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextFactory.java @@ -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); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextType.java similarity index 51% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextType.java index 1cd2e1ad30..68d0e023c0 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointMvcAdapter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementContextType.java @@ -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> { +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 } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementPortType.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementPortType.java new file mode 100644 index 0000000000..5fd8b57a63 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementPortType.java @@ -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); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnManagementPortCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnManagementPortCondition.java new file mode 100644 index 0000000000..c3627d977e --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnManagementPortCondition.java @@ -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 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); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ReactiveWebManagementContextFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ReactiveWebManagementContextFactory.java new file mode 100644 index 0000000000..08167a00d1 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ReactiveWebManagementContextFactory.java @@ -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(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ServletWebManagementContextFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ServletWebManagementContextFactory.java new file mode 100644 index 0000000000..e663d9d9e3 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ServletWebManagementContextFactory.java @@ -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> combinedClasses = new ArrayList>( + 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(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java index 2cc70cdce8..e3acc9959e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java @@ -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 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. */ diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpoint.java new file mode 100644 index 0000000000..c2b22080bd --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpoint.java @@ -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. + *

+ * If no specific {@code endpoints..*} 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. + *

+ * For instance if {@code endpoints.all.enabled} is {@code false} but + * {@code endpoints..enabled} is {@code true}, the condition will match. + *

+ * 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: + * + *

+ * @Configuration
+ * public class MyAutoConfiguration {
+ *
+ *     @ConditionalOnEnabledEndpoint
+ *     @Bean
+ *     public MyEndpoint myEndpoint() {
+ *         ...
+ *     }
+ *
+ *     @Endpoint(id = "my", enabledByDefault = false)
+ *     static class MyEndpoint { ... }
+ *
+ * }
+ *

+ * + * 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 { + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java index ec4faf729e..940a60501b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java @@ -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 healthIndicators; - - private final List infoContributors; - - private final Collection publicMetrics; - - private final TraceRepository traceRepository; - - public EndpointAutoConfiguration(ObjectProvider healthAggregator, - ObjectProvider> healthIndicators, - ObjectProvider> infoContributors, - ObjectProvider> publicMetrics, - ObjectProvider 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, + ObjectProvider> healthIndicators) { return new HealthEndpoint( - this.healthAggregator == null ? new OrderedHealthAggregator() - : this.healthAggregator, - this.healthIndicators == null - ? Collections.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.emptyList() : this.infoContributors); + @ConditionalOnEnabledEndpoint + public InfoEndpoint infoEndpoint( + ObjectProvider> 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 = new ArrayList<>(); - if (this.publicMetrics != null) { - publicMetrics.addAll(this.publicMetrics); - } - publicMetrics.sort(AnnotationAwareOrderComparator.INSTANCE); - return new MetricsEndpoint(publicMetrics); + @ConditionalOnEnabledEndpoint + public MetricsEndpoint metricsEndpoint( + ObjectProvider> publicMetrics) { + List 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) { + 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 flyways) { return new FlywayEndpoint(flyways); } @@ -206,6 +206,7 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public LiquibaseEndpoint liquibaseEndpoint( Map liquibases) { return new LiquibaseEndpoint(liquibases); @@ -219,6 +220,7 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint public RequestMappingEndpoint requestMappingEndpoint() { RequestMappingEndpoint endpoint = new RequestMappingEndpoint(); return endpoint; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfiguration.java deleted file mode 100644 index 811004e5a8..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfiguration.java +++ /dev/null @@ -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) { - 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")); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportProperties.java deleted file mode 100644 index b415742fb6..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportProperties.java +++ /dev/null @@ -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, "="); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfiguration.java deleted file mode 100644 index e4194080e6..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfiguration.java +++ /dev/null @@ -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("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 { - - 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); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfiguration.java deleted file mode 100644 index d68fb4f8c3..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfiguration.java +++ /dev/null @@ -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 mappingCustomizers; - - public EndpointWebMvcManagementContextConfiguration( - HealthMvcEndpointProperties healthMvcEndpointProperties, - ManagementServerProperties managementServerProperties, - EndpointCorsProperties corsProperties, - ObjectProvider> mappingCustomizers) { - this.healthMvcEndpointProperties = healthMvcEndpointProperties; - this.managementServerProperties = managementServerProperties; - this.corsProperties = corsProperties; - List providedCustomizers = mappingCustomizers - .getIfAvailable(); - this.mappingCustomizers = providedCustomizers == null - ? Collections.emptyList() - : providedCustomizers; - } - - @Bean - @ConditionalOnMissingBean - public EndpointHandlerMapping endpointHandlerMapping() { - Set 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()); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextResolver.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextResolver.java index 1be0288559..9318eb69b0 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextResolver.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextResolver.java @@ -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} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/OnEnabledEndpointCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/OnEnabledEndpointCondition.java new file mode 100644 index 0000000000..f99ad69b02 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/OnEnabledEndpointCondition.java @@ -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; + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorMediaTypes.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ActuatorMediaTypes.java similarity index 94% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorMediaTypes.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ActuatorMediaTypes.java index 6817a398a3..8b4dcfa5e7 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorMediaTypes.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ActuatorMediaTypes.java @@ -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 { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactory.java new file mode 100644 index 0000000000..e7df115776 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactory.java @@ -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 { + + 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); + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java new file mode 100644 index 0000000000..6bd648e01e --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/DefaultEndpointObjectNameFactory.java @@ -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()); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java new file mode 100644 index 0000000000..9bd3a32bf4 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointInfrastructureAutoConfiguration.java @@ -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) { + EndpointProvider 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 webEndpointProvider( + OperationParameterMapper operationParameterMapper, + CachingConfigurationFactory cachingConfigurationFactory) { + return new EndpointProvider<>(this.applicationContext.getEnvironment(), + webEndpointDiscoverer(operationParameterMapper, + cachingConfigurationFactory), + EndpointType.WEB); + } + + private WebAnnotationEndpointDiscoverer webEndpointDiscoverer( + OperationParameterMapper operationParameterMapper, + CachingConfigurationFactory cachingConfigurationFactory) { + List mediaTypes = Arrays.asList( + ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON_VALUE, + "application/json"); + return new WebAnnotationEndpointDiscoverer(this.applicationContext, + operationParameterMapper, cachingConfigurationFactory, mediaTypes, + mediaTypes); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointProvider.java new file mode 100644 index 0000000000..ab7eb14f56 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/EndpointProvider.java @@ -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 the endpoint operation type + * @author Stephane Nicoll + * @since 2.0.0 + */ +public final class EndpointProvider { + + private final EndpointDiscoverer 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 discoverer, + EndpointType endpointType) { + this.discoverer = discoverer; + this.endpointEnablementProvider = new EndpointEnablementProvider(environment); + this.endpointType = endpointType; + } + + public Collection> 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(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporter.java new file mode 100644 index 0000000000..d1a4e2b04c --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointExporter.java @@ -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 endpointProvider; + + private final EndpointMBeanRegistrar endpointMBeanRegistrar; + + private final JmxEndpointMBeanFactory mBeanFactory; + + private Collection registeredObjectNames; + + JmxEndpointExporter(EndpointProvider 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 registerEndpointMBeans() { + List objectNames = new ArrayList<>(); + Collection mBeans = this.mBeanFactory.createMBeans( + this.endpointProvider.getEndpoints()); + for (EndpointMBean mBean : mBeans) { + objectNames.add(this.endpointMBeanRegistrar.registerEndpointMBean(mBean)); + } + return objectNames; + } + + private void unregisterEndpointMBeans(Collection 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; + } + + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ManagementWebServerFactoryCustomizer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ManagementWebServerFactoryCustomizer.java new file mode 100644 index 0000000000..3527e82c06 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ManagementWebServerFactoryCustomizer.java @@ -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 the type of web server factory to customize + * @author Andy Wilkinson + */ +abstract class ManagementWebServerFactoryCustomizer + implements WebServerFactoryCustomizer, Ordered { + + private final ListableBeanFactory beanFactory; + + private final Class> customizerClass; + + protected ManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory, + Class> 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 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.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())); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ReactiveEndpointChildManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ReactiveEndpointChildManagementContextConfiguration.java new file mode 100644 index 0000000000..41596f9c37 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ReactiveEndpointChildManagementContextConfiguration.java @@ -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 { + + ManagementReactiveWebServerFactoryCustomizer(ListableBeanFactory beanFactory) { + super(beanFactory, DefaultReactiveWebServerCustomizer.class); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointAutoConfiguration.java new file mode 100644 index 0000000000..2bc92539af --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointAutoConfiguration.java @@ -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); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcChildContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointChildManagementContextConfiguration.java similarity index 61% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcChildContextConfiguration.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointChildManagementContextConfiguration.java index fda7a8db65..d580f8de2c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcChildContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/ServletEndpointChildManagementContextConfiguration.java @@ -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 resourceConfigCustomizers; + + JerseyEndpointChildContextConfiguration( + List resourceConfigCustomizers) { + this.resourceConfigCustomizers = resourceConfigCustomizers; + } + + @Bean + public ServletRegistrationBean jerseyServletRegistration() { + ServletRegistrationBean 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 { - } - - static class ServerFactoryCustomization implements - WebServerFactoryCustomizer, 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.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())); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointHandlerMappingCustomizer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointHandlerMappingCustomizer.java new file mode 100644 index 0000000000..18dd8b9341 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointHandlerMappingCustomizer.java @@ -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); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointInfrastructureManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointInfrastructureManagementContextConfiguration.java new file mode 100644 index 0000000000..8e687c7b65 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebEndpointInfrastructureManagementContextConfiguration.java @@ -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 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 mappingCustomizers; + + MvcWebEndpointConfiguration( + ObjectProvider> mappingCustomizers) { + this.mappingCustomizers = mappingCustomizers + .getIfUnique(Collections::emptyList); + } + + @Bean + @ConditionalOnMissingBean + public WebEndpointServletHandlerMapping webEndpointServletHandlerMapping( + EndpointProvider 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 provider, + ManagementServerProperties managementServerProperties) { + return new WebEndpointReactiveHandlerMapping( + managementServerProperties.getContextPath(), provider.getEndpoints()); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/package-info.java new file mode 100644 index 0000000000..41a0bc3f57 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/package-info.java @@ -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; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java new file mode 100644 index 0000000000..34e15dde15 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java @@ -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); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/package-info.java similarity index 69% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/package-info.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/package-info.java index dfabe73189..81e2f4611d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/jolokia/package-info.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/package-info.java @@ -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; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablement.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablement.java new file mode 100644 index 0000000000..ea4fe14e7a --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablement.java @@ -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; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProvider.java new file mode 100644 index 0000000000..65fc6c45d7 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProvider.java @@ -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; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/package-info.java similarity index 81% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/package-info.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/package-info.java index e6ec0c3589..0e7ec1a12f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/package-info.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/package-info.java @@ -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; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointExtensionProperties.java similarity index 70% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointProperties.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointExtensionProperties.java index e0a367895d..c8275c4690 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointExtensionProperties.java @@ -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 mapping = new HashMap<>(); + private Map mapping = new HashMap<>(); - public Map getMapping() { + public Map getMapping() { return this.mapping; } - public void setMapping(Map mapping) { + public void setMapping(Map mapping) { this.mapping = mapping; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java new file mode 100644 index 0000000000..9d0f929c82 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java @@ -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()); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/package-info.java new file mode 100644 index 0000000000..9873d97fab --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/package-info.java @@ -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; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/security/ManagementWebSecurityAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/security/ManagementWebSecurityAutoConfiguration.java index a3b593146b..c4e312b8eb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/security/ManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/security/ManagementWebSecurityAutoConfiguration.java @@ -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 endpoints = endpointHandlerMapping.getEndpoints(); + Collection> endpoints = endpointHandlerMapping + .getEndpoints(); Set paths = new LinkedHashSet<>(endpoints.size()); - for (MvcEndpoint endpoint : endpoints) { - String path = endpointHandlerMapping.getPath(endpoint.getPath()); + for (EndpointInfo 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 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.emptySet()); + endpointHandlerMapping = new WebEndpointServletHandlerMapping("", + Collections.emptySet()); } return endpointHandlerMapping; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpoint.java index a1ae48e7dd..f487938dda 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpoint.java @@ -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 endpoints; + // TODO Port to new infrastructure - CloudFoundryDiscoveryMvcEndpoint(Set endpoints) { - super(""); - this.endpoints = endpoints; - } - - @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public Map> links(HttpServletRequest request) { - Map 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 endpoints; + // + // CloudFoundryDiscoveryMvcEndpoint(Set endpoints) { + // this.endpoints = endpoints; + // } + // + // @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) + // @ResponseBody + // public Map> links(HttpServletRequest request) { + // Map 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; + // } + // + // } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMapping.java deleted file mode 100644 index 42faa7cdee..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMapping.java +++ /dev/null @@ -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 { - - public CloudFoundryEndpointHandlerMapping(Set endpoints, - CorsConfiguration corsConfiguration, HandlerInterceptor securityInterceptor) { - super(endpoints, corsConfiguration); - setSecurityInterceptor(securityInterceptor); - } - - @Override - protected void postProcessEndpoints(Set endpoints) { - super.postProcessEndpoints(endpoints); - Iterator 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); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpoint.java index 49c4e10974..1b5e202f4d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpoint.java @@ -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; + // } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java index 9e12a28861..3a766085a7 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java @@ -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"); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/ConditionalOnEnabledEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/ConditionalOnEnabledEndpoint.java deleted file mode 100644 index 1363e1fc8a..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/ConditionalOnEnabledEndpoint.java +++ /dev/null @@ -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..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; - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java deleted file mode 100644 index 6f0df8ea88..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java +++ /dev/null @@ -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); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractEndpoint.java deleted file mode 100644 index b3debdedf6..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractEndpoint.java +++ /dev/null @@ -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 the endpoint data type - * @author Phillip Webb - * @author Christian Dupuis - */ -public abstract class AbstractEndpoint implements Endpoint, 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; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpoint.java new file mode 100644 index 0000000000..9468dad189 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpoint.java @@ -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 eventsWithPrincipalDateAfterAndType(String principal, + Date after, String type) { + return this.auditEventRepository.find(principal, after, type); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java index 313a23d605..b80f99ec81 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java @@ -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 { +@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); } /** diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/BeansEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/BeansEndpoint.java index e646f4580d..54b7e79939 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/BeansEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/BeansEndpoint.java @@ -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> - 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> } } - @Override - public List invoke() { + @ReadOperation + public List beans() { return this.parser.parseList(this.liveBeansView.getSnapshotAsJson()); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpoint.java index 8934b42a8b..fa8d5264b2 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpoint.java @@ -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> 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 invoke() { + @ReadOperation + public Map configurationProperties() { return extract(this.context); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Endpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Endpoint.java deleted file mode 100644 index 3c2647ba3f..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Endpoint.java +++ /dev/null @@ -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 the endpoint data type - * @author Phillip Webb - * @author Dave Syer - * @author Christian Dupuis - * @see AbstractEndpoint - */ -public interface Endpoint { - - /** - * 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(); - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java index ab77de36f1..960a2ba183 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpoint.java @@ -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 { +@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 propertyNamePredicate) { PropertyResolver resolver = getResolver(); List 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 namePredicate) { Map 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 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 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 } } + /** + * 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); + } + + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java index 4280ad2497..dc1d839931 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java @@ -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> { +@Endpoint(id = "flyway") +public class FlywayEndpoint { private final Map flyways; public FlywayEndpoint(Map flyways) { - super("flyway"); Assert.notEmpty(flyways, "Flyways must be specified"); this.flyways = flyways; } - @Override - public Map invoke() { + @ReadOperation + public Map flywayReports() { Map reports = new HashMap<>(); for (Map.Entry entry : this.flyways.entrySet()) { reports.put(entry.getKey(), diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java index 3a62ed7801..a352d57b60 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java @@ -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 { +@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 { */ public HealthEndpoint(HealthAggregator healthAggregator, Map 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 { 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(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java index 40cef3f38b..16d779a49a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java @@ -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> { +@Endpoint(id = "info") +public class InfoEndpoint { private final List infoContributors; @@ -41,13 +42,12 @@ public class InfoEndpoint extends AbstractEndpoint> { * @param infoContributors the info contributors to use */ public InfoEndpoint(List infoContributors) { - super("info"); Assert.notNull(infoContributors, "Info contributors must not be null"); this.infoContributors = infoContributors; } - @Override - public Map invoke() { + @ReadOperation + public Map info() { Info.Builder builder = new Info.Builder(); for (InfoContributor contributor : this.infoContributors) { contributor.contribute(builder); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java index c36d989ca6..520b799d5c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java @@ -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> { +@Endpoint(id = "liquibase") +public class LiquibaseEndpoint { private final Map liquibases; - public LiquibaseEndpoint(SpringLiquibase liquibase) { - this(Collections.singletonMap("default", liquibase)); - } - public LiquibaseEndpoint(Map liquibases) { - super("liquibase"); Assert.notEmpty(liquibases, "Liquibases must be specified"); this.liquibases = liquibases; } - @Override - public Map invoke() { + @ReadOperation + public Map liquibaseReports() { Map reports = new HashMap<>(); DatabaseFactory factory = DatabaseFactory.getInstance(); StandardChangeLogHistoryService service = new StandardChangeLogHistoryService(); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java index 7fe2270fef..3d2af7fc70 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LoggersEndpoint.java @@ -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> { +@Endpoint(id = "loggers") +public class LoggersEndpoint { private final LoggingSystem loggingSystem; @@ -47,13 +50,12 @@ public class LoggersEndpoint extends AbstractEndpoint> { * @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 invoke() { + @ReadOperation + public Map loggers() { Collection configurations = this.loggingSystem .getLoggerConfigurations(); if (configurations == null) { @@ -65,6 +67,20 @@ public class LoggersEndpoint extends AbstractEndpoint> { 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 getLevels() { Set levels = this.loggingSystem.getSupportedLogLevels(); return new TreeSet<>(levels).descendingSet(); @@ -79,18 +95,6 @@ public class LoggersEndpoint extends AbstractEndpoint> { 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. */ diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java index 612cdefa8a..0631679715 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java @@ -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> { +@Endpoint(id = "metrics") +public class MetricsEndpoint { private final List publicMetrics; @@ -52,7 +57,6 @@ public class MetricsEndpoint extends AbstractEndpoint> { * {@link AnnotationAwareOrderComparator}. */ public MetricsEndpoint(Collection 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> { this.publicMetrics.remove(metrics); } - @Override - public Map invoke() { + @ReadOperation + public Map metrics(String pattern) { + return metrics(StringUtils.hasText(pattern) + ? Pattern.compile(pattern).asPredicate() : (name) -> true); + } + + @ReadOperation + public Map metricNamed(@Selector String requiredName) { + Map metrics = metrics((name) -> name.equals(requiredName)); + if (metrics.isEmpty()) { + return null; + } + return metrics; + } + + private Map metrics(Predicate namePredicate) { Map result = new LinkedHashMap<>(); List 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) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java index ee1eabbc2a..381956290f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java @@ -44,7 +44,7 @@ public class MetricsEndpointMetricReader implements MetricReader { @Override public Metric findOne(String metricName) { Metric 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> findAll() { List> metrics = new ArrayList<>(); - Map values = this.endpoint.invoke(); + Map values = this.endpoint.metrics(null); Date timestamp = new Date(); for (Entry 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(); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/NamePatternFilter.java similarity index 97% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilter.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/NamePatternFilter.java index db2ed158d0..141cf9c2b1 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/NamePatternFilter.java @@ -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 { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java index c7caece589..ddc851522b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java @@ -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> - implements ApplicationContextAware { +@Endpoint(id = "mappings") +public class RequestMappingEndpoint implements ApplicationContextAware { private List handlerMappings = Collections.emptyList(); @@ -49,10 +49,6 @@ public class RequestMappingEndpoint extends AbstractEndpoint private ApplicationContext applicationContext; - public RequestMappingEndpoint() { - super("mappings"); - } - @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -75,8 +71,8 @@ public class RequestMappingEndpoint extends AbstractEndpoint this.methodMappings = methodMappings; } - @Override - public Map invoke() { + @ReadOperation + public Map mappings() { Map result = new LinkedHashMap<>(); extractHandlerMappings(this.handlerMappings, result); extractHandlerMappings(this.applicationContext, result); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java index 6c36ecc843..8026566337 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ShutdownEndpoint.java @@ -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> - implements ApplicationContextAware { +@Endpoint(id = "shutdown", enabledByDefault = false) +public class ShutdownEndpoint implements ApplicationContextAware { private static final Map NO_CONTEXT_MESSAGE = Collections .unmodifiableMap(Collections.singletonMap("message", @@ -46,15 +46,8 @@ public class ShutdownEndpoint extends AbstractEndpoint> private ConfigurableApplicationContext context; - /** - * Create a new {@link ShutdownEndpoint} instance. - */ - public ShutdownEndpoint() { - super("shutdown", false); - } - - @Override - public Map invoke() { + @WriteOperation + public Map shutdown() { if (this.context == null) { return NO_CONTEXT_MESSAGE; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DumpEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpoint.java similarity index 73% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DumpEndpoint.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpoint.java index 423b4921e1..7e62399850 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DumpEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpoint.java @@ -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> { +@Endpoint(id = "threaddump") +public class ThreadDumpEndpoint { - /** - * Create a new {@link DumpEndpoint} instance. - */ - public DumpEndpoint() { - super("dump"); - } - - @Override - public List invoke() { + @ReadOperation + public List threadDump() { return Arrays .asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/TraceEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/TraceEndpoint.java index eb30b9e64e..11f2291e1b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/TraceEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/TraceEndpoint.java @@ -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> { +@Endpoint(id = "trace") +public class TraceEndpoint { private final TraceRepository repository; @@ -38,13 +39,12 @@ public class TraceEndpoint extends AbstractEndpoint> { * @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 invoke() { + @ReadOperation + public List traces() { return this.repository.findAll(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java deleted file mode 100644 index d21ba8e6ea..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java +++ /dev/null @@ -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 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); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java deleted file mode 100644 index d8690d09f5..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java +++ /dev/null @@ -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 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 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 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); - } - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtension.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtension.java new file mode 100644 index 0000000000..88629f32f9 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtension.java @@ -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 eventsWithDateAfter(Date dateAfter) { + return this.delegate.eventsWithPrincipalDateAfterAndType(null, dateAfter, null); + } + + @ReadOperation + public List eventsWithPrincipalAndDateAfter(String principal, + Date dateAfter) { + return this.delegate.eventsWithPrincipalDateAfterAndType(principal, dateAfter, + null); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java deleted file mode 100644 index 0c519cc5b2..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java +++ /dev/null @@ -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); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java deleted file mode 100644 index c3eccc8a0e..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java +++ /dev/null @@ -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()); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java deleted file mode 100644 index ce6e3e71d7..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java +++ /dev/null @@ -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 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); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java deleted file mode 100644 index 2907591d31..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java +++ /dev/null @@ -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> 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 endpoints) { - for (Map.Entry 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 endpoints) { - for (Map.Entry 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 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(); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java deleted file mode 100644 index 64a018eed0..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java +++ /dev/null @@ -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 getEndpointType(); - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java deleted file mode 100644 index 9a5fc17082..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java +++ /dev/null @@ -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(); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java deleted file mode 100644 index 8541086fe5..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java +++ /dev/null @@ -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()); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMapping.java deleted file mode 100644 index 45b16f2fd8..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMapping.java +++ /dev/null @@ -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). - *

- * 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 The endpoint type - * @author Phillip Webb - * @author Christian Dupuis - * @author Dave Syer - * @author Madhura Bhave - */ -public abstract class AbstractEndpointHandlerMapping - extends RequestMappingHandlerMapping { - - private final Set 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 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 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 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 defaultPatterns = mapping.getPatternsCondition().getPatterns(); - if (defaultPatterns.isEmpty()) { - return new String[] { patternPrefix, patternPrefix + ".json" }; - } - List 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 interceptors) { - interceptors.add(new SkipPathExtensionContentNegotiation()); - } - - private HandlerExecutionChain addSecurityInterceptor(HandlerExecutionChain chain) { - List 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 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; - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java deleted file mode 100644 index f3e60d2123..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java +++ /dev/null @@ -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 The delegate endpoint - * @author Dave Syer - * @author Andy Wilkinson - * @author Phillip Webb - * @since 1.3.0 - */ -public abstract class AbstractEndpointMvcAdapter> - 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 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; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractMvcEndpoint.java deleted file mode 100644 index efff32ed5c..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractMvcEndpoint.java +++ /dev/null @@ -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 getEndpointType() { - return null; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java deleted file mode 100644 index 353070ad12..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorPostMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorPostMapping.java deleted file mode 100644 index ff4746ba2d..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ActuatorPostMapping.java +++ /dev/null @@ -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 {}; - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpoint.java deleted file mode 100644 index 38fce97c76..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpoint.java +++ /dev/null @@ -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 result = new LinkedHashMap<>(); - result.put("events", this.auditEventRepository.find(principal, after, type)); - return ResponseEntity.ok(result); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java deleted file mode 100644 index d5c153f427..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java +++ /dev/null @@ -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). - *

- * 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 { - - /** - * 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 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 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 endpoints; - - private EndpointLinksMvcEndpoint(Set endpoints) { - super(""); - this.endpoints = endpoints; - } - - @ResponseBody - @ActuatorGetMapping - public Map> links(HttpServletRequest request) { - Map 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()); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java deleted file mode 100644 index db35991038..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java +++ /dev/null @@ -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 { - - 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); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java deleted file mode 100644 index de94480178..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpoint.java +++ /dev/null @@ -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 { - - private static final List DEFAULT_ROLES = Arrays.asList("ROLE_ACTUATOR"); - - private final boolean secure; - - private final List roles; - - private Map 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 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 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 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 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 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; - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java deleted file mode 100644 index 4971c86c12..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java +++ /dev/null @@ -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; - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java deleted file mode 100644 index e603a20821..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpoint.java +++ /dev/null @@ -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 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 configuration) { - String level = configuration.get("configuredLevel"); - return (level == null ? null : LogLevel.valueOf(level.toUpperCase())); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java index 3eda218938..23389b9f9d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java @@ -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 */ diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java deleted file mode 100644 index bbf74a31ad..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java +++ /dev/null @@ -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> { - - NamePatternMapFilter(Map source) { - super(source); - } - - @Override - protected void getNames(Map 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 source, String name) { - return source.get(name); - } - - @Override - protected Object getValue(Map 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); - } - - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java deleted file mode 100644 index 969bfcfc90..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java +++ /dev/null @@ -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> 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 getEndpointType(); - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java deleted file mode 100644 index 7430b36d5d..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java +++ /dev/null @@ -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 roles; - - private AtomicBoolean loggedUnauthorizedAttempt = new AtomicBoolean(); - - public MvcEndpointSecurityInterceptor(boolean secure, List 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; - } - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java deleted file mode 100644 index fe7d82dd0d..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java +++ /dev/null @@ -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 endpoints = new HashSet<>(); - - private Set> customTypes; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void afterPropertiesSet() throws Exception { - Collection existing = BeanFactoryUtils - .beansOfTypeIncludingAncestors(this.applicationContext, MvcEndpoint.class) - .values(); - this.endpoints.addAll(existing); - this.customTypes = findEndpointClasses(existing); - @SuppressWarnings("rawtypes") - Collection 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> findEndpointClasses(Collection existing) { - Set> types = new HashSet<>(); - for (MvcEndpoint endpoint : existing) { - Class type = endpoint.getEndpointType(); - if (type != null) { - types.add(type); - } - } - return types; - } - - public Set getEndpoints() { - return this.endpoints; - } - - /** - * Return the endpoints of the specified type. - * @param the Class type of the endpoints to be returned - * @param type the endpoint type - * @return the endpoints - */ - @SuppressWarnings("unchecked") - public Set getEndpoints(Class type) { - Set 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; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java deleted file mode 100644 index f92fb0eecf..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java +++ /dev/null @@ -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(); - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java deleted file mode 100644 index 6424d102b0..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java +++ /dev/null @@ -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(); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/package-info.java index 32a319eb89..f3e4a76aba 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/package-info.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/package-info.java @@ -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; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtension.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtension.java new file mode 100644 index 0000000000..2f955c1bef --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtension.java @@ -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> eventsWithPrincipalDateAfterAndType( + String principal, Date after, String type) { + return Collections.singletonMap("events", this.delegate + .eventsWithPrincipalDateAfterAndType(principal, after, type)); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthWebEndpointExtension.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthWebEndpointExtension.java new file mode 100644 index 0000000000..3848e47747 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthWebEndpointExtension.java @@ -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 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 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 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 getHealth() { + Health health = this.delegate.health(); + Integer status = getStatus(health); + return new WebEndpointResponse(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(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpoint.java similarity index 57% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpoint.java index 03a1300d4c..43f16df6aa 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpoint.java @@ -14,48 +14,45 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; +package org.springframework.boot.actuate.endpoint.web; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; import java.lang.management.PlatformManagedObject; import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.zip.GZIPOutputStream; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.web.WebEndpointResponse; +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.util.ClassUtils; import org.springframework.util.ReflectionUtils; -import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; /** - * {@link MvcEndpoint} to expose heap dumps. + * Web {@link Endpoint} to expose heap dumps. * * @author Lari Hotari * @author Phillip Webb * @author Raja Kolli - * @since 1.4.0 + * @author Andy Wilkinson + * @since 2.0.0 */ @ConfigurationProperties(prefix = "endpoints.heapdump") -public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { +@Endpoint(id = "heapdump", types = EndpointType.WEB) +public class HeapDumpWebEndpoint { private final long timeout; @@ -63,28 +60,21 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { private HeapDumper heapDumper; - public HeapdumpMvcEndpoint() { + public HeapDumpWebEndpoint() { this(TimeUnit.SECONDS.toMillis(10)); } - protected HeapdumpMvcEndpoint(long timeout) { - super("heapdump", "/heapdump"); + protected HeapDumpWebEndpoint(long timeout) { this.timeout = timeout; } - @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public void invoke(@RequestParam(defaultValue = "true") boolean live, - HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - if (!isEnabled()) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return; - } + @ReadOperation + public WebEndpointResponse heapDump(Boolean live) { try { if (this.lock.tryLock(this.timeout, TimeUnit.MILLISECONDS)) { try { - dumpHeap(live, request, response); - return; + return new WebEndpointResponse<>( + dumpHeap(live == null ? true : live)); } finally { this.lock.unlock(); @@ -94,23 +84,22 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { catch (InterruptedException ex) { Thread.currentThread().interrupt(); } - response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); + catch (IOException ex) { + return new WebEndpointResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + catch (HeapDumperUnavailableException ex) { + return new WebEndpointResponse<>(HttpStatus.SERVICE_UNAVAILABLE.value()); + } + return new WebEndpointResponse<>(HttpStatus.TOO_MANY_REQUESTS.value()); } - private void dumpHeap(boolean live, HttpServletRequest request, - HttpServletResponse response) - throws IOException, ServletException, InterruptedException { + private Resource dumpHeap(boolean live) throws IOException, InterruptedException { if (this.heapDumper == null) { this.heapDumper = createHeapDumper(); } File file = createTempFile(live); - try { - this.heapDumper.dumpHeap(file, live); - handle(file, request, response); - } - finally { - file.delete(); - } + this.heapDumper.dumpHeap(file, live); + return new TemporaryFileSystemResource(file); } private File createTempFile(boolean live) throws IOException { @@ -130,29 +119,6 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { return new HotSpotDiagnosticMXBeanHeapDumper(); } - /** - * Handle the heap dump file and respond. By default this method will return the - * response as a GZip stream. - * @param heapDumpFile the generated dump file - * @param request the HTTP request - * @param response the HTTP response - * @throws ServletException on servlet error - * @throws IOException on IO error - */ - protected void handle(File heapDumpFile, HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - response.setContentType("application/octet-stream"); - response.setHeader("Content-Disposition", - "attachment; filename=\"" + (heapDumpFile.getName() + ".gz") + "\""); - try (InputStream in = new FileInputStream(heapDumpFile); - GZIPOutputStream out = new GZIPOutputStream(response.getOutputStream())) { - StreamUtils.copy(in, out); - out.finish(); - } - catch (NullPointerException | FileNotFoundException ex) { - } - } - /** * Strategy interface used to dump the heap to a file. */ @@ -208,7 +174,6 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { /** * Exception to be thrown if the {@link HeapDumper} cannot be created. */ - @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) protected static class HeapDumperUnavailableException extends RuntimeException { public HeapDumperUnavailableException(String message, Throwable cause) { @@ -217,4 +182,104 @@ public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { } + private static final class TemporaryFileSystemResource extends FileSystemResource { + + private TemporaryFileSystemResource(File file) { + super(file); + } + + @Override + public ReadableByteChannel readableChannel() throws IOException { + ReadableByteChannel readableChannel = super.readableChannel(); + return new ReadableByteChannel() { + + @Override + public boolean isOpen() { + return readableChannel.isOpen(); + } + + @Override + public void close() throws IOException { + try { + readableChannel.close(); + } + finally { + getFile().delete(); + } + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return readableChannel.read(dst); + } + + }; + } + + @Override + public InputStream getInputStream() throws IOException { + InputStream delegate = super.getInputStream(); + return new InputStream() { + + @Override + public int read() throws IOException { + return delegate.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return delegate.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return delegate.skip(n); + } + + @Override + public int available() throws IOException { + return delegate.available(); + } + + @Override + public void close() throws IOException { + try { + delegate.close(); + } + finally { + getFile().delete(); + } + } + + @Override + public synchronized void mark(int readlimit) { + delegate.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + delegate.reset(); + } + + @Override + public boolean markSupported() { + return delegate.markSupported(); + } + + }; + } + + @Override + public boolean isFile() { + // Prevent zero-copy so we can delete the file on close + return false; + } + + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpoint.java new file mode 100644 index 0000000000..d82ee0c4af --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpoint.java @@ -0,0 +1,88 @@ +/* + * 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.io.File; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.endpoint.Endpoint; +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.logging.LogFile; +import org.springframework.core.env.Environment; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +/** + * Web {@link Endpoint} that provides access to an application's log file. + * + * @author Johannes Edmeier + * @author Phillip Webb + * @author Andy Wilkinson + * @since 2.0.0 + */ +@ConfigurationProperties(prefix = "endpoints.logfile") +@Endpoint(id = "logfile", types = EndpointType.WEB) +public class LogFileWebEndpoint { + + private static final Log logger = LogFactory.getLog(LogFileWebEndpoint.class); + + private final Environment environment; + + /** + * 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 LogFileWebEndpoint(Environment environment) { + this.environment = environment; + } + + public File getExternalFile() { + return this.externalFile; + } + + public void setExternalFile(File externalFile) { + this.externalFile = externalFile; + } + + @ReadOperation + public Resource logFile() { + Resource logFileResource = getLogFileResource(); + if (logFileResource == null || !logFileResource.isReadable()) { + return null; + } + return logFileResource; + } + + private Resource getLogFileResource() { + if (this.externalFile != null) { + return new FileSystemResource(this.externalFile); + } + LogFile logFile = LogFile.get(this.environment); + if (logFile == null) { + logger.debug("Missing 'logging.file' or 'logging.path' properties"); + return null; + } + return new FileSystemResource(logFile.toString()); + } + +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 81d9a70ceb..b6465391e2 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -1,22 +1,27 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.cache.CacheStatisticsAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryActuatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.endpoint.EndpointMBeanExportAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricFilterAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricRepositoryAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricsDropwizardAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricsChannelAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.MetricExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.PublicMetricsAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.trace.TraceRepositoryAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.trace.TraceWebFilterAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryActuatorAutoConfiguration +org.springframework.boot.actuate.autoconfigure.trace.TraceWebFilterAutoConfiguration org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=\ -org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcManagementContextConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ReactiveEndpointChildManagementContextConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointChildManagementContextConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.WebEndpointInfrastructureManagementContextConfiguration,\ +org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointManagementContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointServletWebAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointServletWebAutoConfigurationTests.java new file mode 100755 index 0000000000..51395a8e85 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointServletWebAutoConfigurationTests.java @@ -0,0 +1,32 @@ +/* + * 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.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; + +/** + * Tests for {@link ServletEndpointAutoConfiguration}. + * + * @author Phillip Webb + * @author Greg Turnquist + * @author Andy Wilkinson + * @author EddĂș MelĂ©ndez + * @author Ben Hale + */ +public class EndpointServletWebAutoConfigurationTests { + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelectorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelectorTests.java new file mode 100644 index 0000000000..692518df5b --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementContextConfigurationImportSelectorTests.java @@ -0,0 +1,123 @@ +/* + * 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.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Test; + +import org.springframework.core.annotation.Order; +import org.springframework.core.type.StandardAnnotationMetadata; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ManagementContextConfigurationImportSelector}. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class ManagementContextConfigurationImportSelectorTests { + + @Test + public void selectImportsShouldOrderResult() throws Exception { + String[] imports = new TestManagementContextConfigurationsImportSelector(C.class, + A.class, D.class, B.class).selectImports( + new StandardAnnotationMetadata(EnableChildContext.class)); + assertThat(imports).containsExactly(A.class.getName(), B.class.getName(), + C.class.getName(), D.class.getName()); + } + + @Test + public void selectImportsFiltersChildOnlyConfigurationWhenUsingSameContext() + throws Exception { + String[] imports = new TestManagementContextConfigurationsImportSelector( + ChildOnly.class, SameOnly.class, A.class).selectImports( + new StandardAnnotationMetadata(EnableSameContext.class)); + assertThat(imports).containsExactlyInAnyOrder(SameOnly.class.getName(), + A.class.getName()); + } + + @Test + public void selectImportsFiltersSameOnlyConfigurationWhenUsingChildContext() + throws Exception { + String[] imports = new TestManagementContextConfigurationsImportSelector( + ChildOnly.class, SameOnly.class, A.class).selectImports( + new StandardAnnotationMetadata(EnableChildContext.class)); + assertThat(imports).containsExactlyInAnyOrder(ChildOnly.class.getName(), + A.class.getName()); + } + + private static final class TestManagementContextConfigurationsImportSelector + extends ManagementContextConfigurationImportSelector { + + private final List factoryNames; + + private TestManagementContextConfigurationsImportSelector(Class... classes) { + this.factoryNames = Stream.of(classes).map(Class::getName) + .collect(Collectors.toList()); + } + + @Override + protected List loadFactoryNames() { + return this.factoryNames; + } + + } + + @Order(1) + private static class A { + + } + + @Order(2) + private static class B { + + } + + @Order(3) + private static class C { + + } + + static class D { + + } + + @ManagementContextConfiguration(ManagementContextType.CHILD) + static class ChildOnly { + + } + + @ManagementContextConfiguration(ManagementContextType.SAME) + static class SameOnly { + + } + + @EnableManagementContext(ManagementContextType.CHILD) + static class EnableChildContext { + + } + + @EnableManagementContext(ManagementContextType.SAME) + static class EnableSameContext { + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java index 9e3368355b..3d07c0b71d 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java @@ -19,7 +19,7 @@ package org.springframework.boot.actuate.autoconfigure; import org.junit.After; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointMBeanExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; @@ -73,13 +73,13 @@ public class SpringApplicationHierarchyTests { } - @EnableAutoConfiguration(exclude = { EndpointMBeanExportAutoConfiguration.class, - ElasticsearchDataAutoConfiguration.class, + @EnableAutoConfiguration(exclude = { ElasticsearchDataAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, MongoDataAutoConfiguration.class, Neo4jDataAutoConfiguration.class, Neo4jRepositoriesAutoConfiguration.class, RedisAutoConfiguration.class, - RedisRepositoriesAutoConfiguration.class }, excludeName = { + RedisRepositoriesAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class }, excludeName = { "org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration" }) public static class Parent { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpointTests.java new file mode 100644 index 0000000000..bb956f81f9 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ConditionalOnEnabledEndpointTests.java @@ -0,0 +1,364 @@ +/* + * 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.junit.Test; + +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.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnEnabledEndpoint}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +public class ConditionalOnEnabledEndpointTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + public void enabledByDefault() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void disabledViaSpecificProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.foo.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("foo")); + } + + @Test + public void disabledViaGeneralProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("foo")); + } + + @Test + public void enabledOverrideViaSpecificProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.foo.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaSpecificWebProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.foo.enabled=false", + "endpoints.foo.web.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaSpecificJmxProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.foo.enabled=false", + "endpoints.foo.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaSpecificAnyProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.foo.enabled=false", + "endpoints.foo.web.enabled=false", + "endpoints.foo.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaGeneralWebProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.all.web.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaGeneralJmxProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.all.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void enabledOverrideViaGeneralAnyProperty() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.all.web.enabled=false", + "endpoints.all.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void disabledEvenWithEnabledGeneralProperties() { + this.contextRunner.withUserConfiguration(FooConfig.class) + .withPropertyValues("endpoints.all.enabled=true", + "endpoints.all.web.enabled=true", + "endpoints.all.jmx.enabled=true", "endpoints.foo.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("foo")); + } + + @Test + public void disabledByDefaultWithAnnotationFlag() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + public void disabledByDefaultWithAnnotationFlagEvenWithGeneralProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.all.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + public void disabledByDefaultWithAnnotationFlagEvenWithGeneralWebProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.all.web.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + public void disabledByDefaultWithAnnotationFlagEvenWithGeneralJmxProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.all.jmx.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + public void enabledOverrideWithAndAnnotationFlagAndSpecificProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.bar.enabled=true") + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + public void enabledOverrideWithAndAnnotationFlagAndSpecificWebProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.bar.web.enabled=true") + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + public void enabledOverrideWithAndAnnotationFlagAndSpecificJmxProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.bar.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + public void enabledOverrideWithAndAnnotationFlagAndAnyProperty() { + this.contextRunner.withUserConfiguration(BarConfig.class) + .withPropertyValues("endpoints.bar.web.enabled=false", + "endpoints.bar.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + public void enabledOnlyWebByDefault() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .run((context) -> assertThat(context).hasBean("onlyweb")); + } + + @Test + public void disabledOnlyWebViaEndpointProperty() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.onlyweb.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("onlyweb")); + } + + @Test + public void disabledOnlyWebViaSpecificTechProperty() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.onlyweb.web.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("onlyweb")); + } + + @Test + public void enableOverridesOnlyWebViaGeneralJmxPropertyHasNoEffect() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.all.jmx.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean("onlyweb")); + } + + @Test + public void enableOverridesOnlyWebViaSpecificJmxPropertyHasNoEffect() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.onlyweb.jmx.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("onlyweb")); + } + + @Test + public void enableOverridesOnlyWebViaSpecificWebProperty() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class) + .withPropertyValues("endpoints.all.enabled=false", + "endpoints.onlyweb.web.enabled=true") + .run((context) -> assertThat(context).hasBean("onlyweb")); + } + + @Test + public void disabledOnlyWebEvenWithEnabledGeneralProperties() { + this.contextRunner.withUserConfiguration(OnlyWebConfig.class).withPropertyValues( + "endpoints.all.enabled=true", "endpoints.all.web.enabled=true", + "endpoints.onlyweb.enabled=true", "endpoints.onlyweb.web.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean("foo")); + } + + @Test + public void contextFailIfEndpointTypeIsNotDetected() { + this.contextRunner.withUserConfiguration(NonEndpointBeanConfig.class) + .run((context) -> assertThat(context).hasFailed()); + } + + @Test + public void webExtensionWithEnabledByDefaultEndpoint() { + this.contextRunner.withUserConfiguration(FooWebExtensionConfig.class) + .run((context) -> assertThat(context) + .hasSingleBean(FooWebEndpointExtension.class)); + } + + @Test + public void webExtensionWithEnabledByDefaultEndpointCanBeDisabled() { + this.contextRunner.withUserConfiguration(FooWebExtensionConfig.class) + .withPropertyValues("endpoints.foo.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(FooWebEndpointExtension.class)); + } + + @Test + public void jmxExtensionWithEnabledByDefaultEndpoint() { + this.contextRunner.withUserConfiguration(FooJmxExtensionConfig.class) + .run((context) -> assertThat(context) + .hasSingleBean(FooJmxEndpointExtension.class)); + } + + @Test + public void jmxExtensionWithEnabledByDefaultEndpointCanBeDisabled() { + this.contextRunner.withUserConfiguration(FooJmxExtensionConfig.class) + .withPropertyValues("endpoints.foo.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(FooJmxEndpointExtension.class)); + } + + @Configuration + static class FooConfig { + + @Bean + @ConditionalOnEnabledEndpoint + public FooEndpoint foo() { + return new FooEndpoint(); + } + + } + + @Endpoint(id = "foo") + static class FooEndpoint { + + } + + @Configuration + static class BarConfig { + + @Bean + @ConditionalOnEnabledEndpoint + public BarEndpoint bar() { + return new BarEndpoint(); + } + + } + + @Endpoint(id = "bar", types = { EndpointType.WEB, + EndpointType.JMX }, enabledByDefault = false) + static class BarEndpoint { + + } + + @Configuration + static class OnlyWebConfig { + + @Bean(name = "onlyweb") + @ConditionalOnEnabledEndpoint + public OnlyWebEndpoint onlyWeb() { + return new OnlyWebEndpoint(); + } + + } + + @Endpoint(id = "onlyweb", types = EndpointType.WEB) + static class OnlyWebEndpoint { + + } + + @Configuration + static class NonEndpointBeanConfig { + + @Bean + @ConditionalOnEnabledEndpoint + public String foo() { + return "endpoint type cannot be detected"; + } + + } + + @JmxEndpointExtension(endpoint = FooEndpoint.class) + static class FooJmxEndpointExtension { + + } + + @Configuration + static class FooJmxExtensionConfig { + + @Bean + @ConditionalOnEnabledEndpoint + FooJmxEndpointExtension fooJmxEndpointExtension() { + return new FooJmxEndpointExtension(); + } + + } + + @WebEndpointExtension(endpoint = FooEndpoint.class) + static class FooWebEndpointExtension { + + } + + @Configuration + static class FooWebExtensionConfig { + + @Bean + @ConditionalOnEnabledEndpoint + FooWebEndpointExtension fooJmxEndpointExtension() { + return new FooWebEndpointExtension(); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java deleted file mode 100644 index c692609c15..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java +++ /dev/null @@ -1,392 +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.io.IOException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Properties; - -import javax.sql.DataSource; - -import liquibase.integration.spring.SpringLiquibase; -import org.flywaydb.core.Flyway; -import org.junit.After; -import org.junit.Test; - -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.PublicMetricsAutoConfiguration; -import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint; -import org.springframework.boot.actuate.endpoint.BeansEndpoint; -import org.springframework.boot.actuate.endpoint.DumpEndpoint; -import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; -import org.springframework.boot.actuate.endpoint.FlywayEndpoint; -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.InfoEndpoint; -import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -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.TraceEndpoint; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.info.Info; -import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; -import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; -import org.springframework.boot.autoconfigure.info.ProjectInfoProperties; -import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.Order; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PropertiesLoaderUtils; -import org.springframework.validation.BindException; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointAutoConfiguration}. - * - * @author Dave Syer - * @author Phillip Webb - * @author Greg Turnquist - * @author Christian Dupuis - * @author Stephane Nicoll - * @author EddĂș MelĂ©ndez - * @author Meang Akira Tanaka - * @author Ben Hale - */ -public class EndpointAutoConfigurationTests { - - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void endpoints() throws Exception { - load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); - assertThat(this.context.getBean(BeansEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(DumpEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(EnvironmentEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(HealthEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(InfoEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(LoggersEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(MetricsEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(ShutdownEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(TraceEndpoint.class)).isNotNull(); - assertThat(this.context.getBean(RequestMappingEndpoint.class)).isNotNull(); - } - - @Test - public void healthEndpoint() { - load(EmbeddedDataSourceConfiguration.class, EndpointAutoConfiguration.class, - HealthIndicatorAutoConfiguration.class); - HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); - assertThat(bean).isNotNull(); - Health result = bean.invoke(); - assertThat(result).isNotNull(); - assertThat(result.getDetails().containsKey("db")).isTrue(); - } - - @Test - public void healthEndpointWithDefaultHealthIndicator() { - load(EndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class); - HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); - assertThat(bean).isNotNull(); - Health result = bean.invoke(); - assertThat(result).isNotNull(); - } - - @Test - public void loggersEndpointHasLoggers() throws Exception { - load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); - LoggersEndpoint endpoint = this.context.getBean(LoggersEndpoint.class); - Map result = endpoint.invoke(); - assertThat((Map) result.get("loggers")).size().isGreaterThan(0); - } - - @Test - public void metricEndpointsHasSystemMetricsByDefault() { - load(PublicMetricsAutoConfiguration.class, EndpointAutoConfiguration.class); - MetricsEndpoint endpoint = this.context.getBean(MetricsEndpoint.class); - Map metrics = endpoint.invoke(); - assertThat(metrics.containsKey("mem")).isTrue(); - assertThat(metrics.containsKey("heap.used")).isTrue(); - } - - @Test - public void metricEndpointCustomPublicMetrics() { - load(CustomPublicMetricsConfig.class, PublicMetricsAutoConfiguration.class, - EndpointAutoConfiguration.class); - MetricsEndpoint endpoint = this.context.getBean(MetricsEndpoint.class); - Map metrics = endpoint.invoke(); - - // Custom metrics - assertThat(metrics.containsKey("foo")).isTrue(); - - // System metrics still available - assertThat(metrics.containsKey("mem")).isTrue(); - assertThat(metrics.containsKey("heap.used")).isTrue(); - - } - - @Test - public void autoConfigurationAuditEndpoints() { - load(EndpointAutoConfiguration.class, ConditionEvaluationReport.class); - assertThat(this.context.getBean(AutoConfigurationReportEndpoint.class)) - .isNotNull(); - } - - @Test - public void testInfoEndpoint() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("info.foo:bar").applyTo(this.context); - this.context.register(ProjectInfoAutoConfiguration.class, - InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - - InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); - assertThat(endpoint).isNotNull(); - assertThat(endpoint.invoke().get("git")).isNotNull(); - assertThat(endpoint.invoke().get("foo")).isEqualTo("bar"); - } - - @Test - public void testInfoEndpointNoGitProperties() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("spring.info.git.location:classpath:nonexistent") - .applyTo(this.context); - this.context.register(InfoContributorAutoConfiguration.class, - EndpointAutoConfiguration.class); - this.context.refresh(); - InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); - assertThat(endpoint).isNotNull(); - assertThat(endpoint.invoke().get("git")).isNull(); - } - - @Test - public void testInfoEndpointOrdering() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("info.name:foo").applyTo(this.context); - this.context.register(CustomInfoContributorsConfig.class, - ProjectInfoAutoConfiguration.class, - InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - - InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); - Map info = endpoint.invoke(); - assertThat(info).isNotNull(); - assertThat(info.get("name")).isEqualTo("foo"); - assertThat(info.get("version")).isEqualTo("1.0"); - Object git = info.get("git"); - assertThat(git).isInstanceOf(Map.class); - } - - @Test - public void testFlywayEndpoint() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EmbeddedDataSourceConfiguration.class, - FlywayAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - FlywayEndpoint endpoint = this.context.getBean(FlywayEndpoint.class); - assertThat(endpoint).isNotNull(); - assertThat(endpoint.invoke()).hasSize(1); - } - - @Test - public void testFlywayEndpointWithMultipleFlywayBeans() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(MultipleFlywayBeansConfig.class, - FlywayAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(Flyway.class)).hasSize(2); - assertThat(this.context.getBeansOfType(FlywayEndpoint.class)).hasSize(1); - } - - @Test - public void testLiquibaseEndpoint() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EmbeddedDataSourceConfiguration.class, - LiquibaseAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - LiquibaseEndpoint endpoint = this.context.getBean(LiquibaseEndpoint.class); - assertThat(endpoint).isNotNull(); - assertThat(endpoint.invoke()).hasSize(1); - } - - @Test - public void testLiquibaseEndpointWithMultipleSpringLiquibaseBeans() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(MultipleLiquibaseBeansConfig.class, - LiquibaseAutoConfiguration.class, EndpointAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(SpringLiquibase.class)).hasSize(2); - assertThat(this.context.getBeansOfType(LiquibaseEndpoint.class)).hasSize(1); - } - - private void load(Class... config) { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(config); - this.context.refresh(); - } - - @Configuration - static class CustomLoggingConfig { - - @Bean - LoggingSystem loggingSystem() { - return LoggingSystem.get(getClass().getClassLoader()); - } - - } - - @Configuration - static class CustomPublicMetricsConfig { - - @Bean - PublicMetrics customPublicMetrics() { - return () -> { - Metric metric = new Metric<>("foo", 1); - return Collections.>singleton(metric); - }; - } - - } - - @Configuration - static class CustomInfoContributorsConfig { - - @Bean - @Order(InfoContributorAutoConfiguration.DEFAULT_ORDER - 1) - public InfoContributor myInfoContributor() { - return (builder) -> { - builder.withDetail("name", "bar"); - builder.withDetail("version", "1.0"); - }; - } - - @Bean - @Order(InfoContributorAutoConfiguration.DEFAULT_ORDER + 1) - public InfoContributor myAnotherContributor(ProjectInfoProperties properties) - throws IOException, BindException { - return new GitFullInfoContributor(properties.getGit().getLocation()); - } - - private static class GitFullInfoContributor implements InfoContributor { - - private static final ResolvableType STRING_OBJECT_MAP = ResolvableType - .forClassWithGenerics(Map.class, String.class, Object.class); - - private Map content = new LinkedHashMap<>(); - - GitFullInfoContributor(Resource location) throws BindException, IOException { - if (!location.exists()) { - return; - } - Properties properties = PropertiesLoaderUtils.loadProperties(location); - new Binder(new MapConfigurationPropertySource(properties)).bind("info", - Bindable.of(STRING_OBJECT_MAP).withExistingValue(this.content)); - } - - @Override - public void contribute(Info.Builder builder) { - if (!this.content.isEmpty()) { - builder.withDetail("git", this.content); - } - } - - } - - } - - static class DataSourceConfig { - - @Bean - public DataSource dataSourceOne() { - return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest") - .username("sa").build(); - } - - @Bean - public DataSource dataSourceTwo() { - return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest2") - .username("sa").build(); - } - - } - - @Configuration - static class MultipleFlywayBeansConfig extends DataSourceConfig { - - @Bean - public Flyway flywayOne() { - Flyway flyway = new Flyway(); - flyway.setDataSource(dataSourceOne()); - return flyway; - } - - @Bean - public Flyway flywayTwo() { - Flyway flyway = new Flyway(); - flyway.setDataSource(dataSourceTwo()); - return flyway; - } - - } - - @Configuration - static class MultipleLiquibaseBeansConfig extends DataSourceConfig { - - @Bean - public SpringLiquibase liquibaseOne() { - SpringLiquibase liquibase = new SpringLiquibase(); - liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); - liquibase.setDataSource(dataSourceOne()); - return liquibase; - } - - @Bean - public SpringLiquibase liquibaseTwo() { - SpringLiquibase liquibase = new SpringLiquibase(); - liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); - liquibase.setDataSource(dataSourceTwo()); - return liquibase; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfigurationTests.java deleted file mode 100644 index 1178e12d13..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMBeanExportAutoConfigurationTests.java +++ /dev/null @@ -1,215 +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.InstanceNotFoundException; -import javax.management.IntrospectionException; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; -import javax.management.ReflectionException; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.EnableMBeanExport; -import org.springframework.jmx.export.MBeanExporter; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.jmx.support.ObjectNameManager; -import org.springframework.mock.env.MockEnvironment; -import org.springframework.stereotype.Component; -import org.springframework.util.ObjectUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointMBeanExportAutoConfiguration}. - * - */ -public class EndpointMBeanExportAutoConfigurationTests { - - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void testEndpointMBeanExporterIsInstalled() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, JmxAutoConfiguration.class, - EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(EndpointMBeanExporter.class)).isNotNull(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .queryNames(getObjectName("*", "*,*", this.context), null)).isNotEmpty(); - } - - @Test - public void testEndpointMBeanExporterIsNotInstalledIfManagedResource() - throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, JmxAutoConfiguration.class, - ManagedEndpoint.class, EndpointMBeanExportAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(EndpointMBeanExporter.class)).isNotNull(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .queryNames(getObjectName("*", "*,*", this.context), null)).isEmpty(); - } - - @Test - public void testEndpointMBeanExporterIsNotInstalledIfNestedInManagedResource() - throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, JmxAutoConfiguration.class, - NestedInManagedEndpoint.class, EndpointMBeanExportAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(EndpointMBeanExporter.class)).isNotNull(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .queryNames(getObjectName("*", "*,*", this.context), null)).isEmpty(); - } - - @Test(expected = NoSuchBeanDefinitionException.class) - public void testEndpointMBeanExporterIsNotInstalled() { - MockEnvironment environment = new MockEnvironment(); - environment.setProperty("endpoints.jmx.enabled", "false"); - this.context = new AnnotationConfigApplicationContext(); - this.context.setEnvironment(environment); - this.context.register(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class); - this.context.refresh(); - this.context.getBean(EndpointMBeanExporter.class); - } - - @Test - public void testEndpointMBeanExporterWithProperties() throws IntrospectionException, - InstanceNotFoundException, MalformedObjectNameException, ReflectionException { - MockEnvironment environment = new MockEnvironment(); - environment.setProperty("endpoints.jmx.domain", "test-domain"); - environment.setProperty("endpoints.jmx.unique_names", "true"); - environment.setProperty("endpoints.jmx.static_names", "key1=value1, key2=value2"); - this.context = new AnnotationConfigApplicationContext(); - this.context.setEnvironment(environment); - this.context.register(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class); - this.context.refresh(); - this.context.getBean(EndpointMBeanExporter.class); - - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer().getMBeanInfo(ObjectNameManager.getInstance( - getObjectName("test-domain", "healthEndpoint", this.context).toString() - + ",key1=value1,key2=value2"))).isNotNull(); - } - - @Test - public void testEndpointMBeanExporterInParentChild() throws IntrospectionException, - InstanceNotFoundException, MalformedObjectNameException, ReflectionException { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class); - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointMBeanExportAutoConfiguration.class); - this.context.setParent(parent); - parent.refresh(); - this.context.refresh(); - parent.close(); - } - - private ObjectName getObjectName(String domain, String beanKey, - ApplicationContext applicationContext) throws MalformedObjectNameException { - String name = "%s:type=Endpoint,name=%s"; - if (applicationContext.getParent() != null) { - name = name + ",context=%s"; - } - if (applicationContext.getEnvironment().getProperty("endpoints.jmx.unique_names", - Boolean.class, false)) { - name = name + ",identity=" + ObjectUtils - .getIdentityHexString(applicationContext.getBean(beanKey)); - } - if (applicationContext.getParent() != null) { - return ObjectNameManager.getInstance(String.format(name, domain, beanKey, - ObjectUtils.getIdentityHexString(applicationContext))); - } - return ObjectNameManager.getInstance(String.format(name, domain, beanKey)); - } - - @Configuration - @EnableMBeanExport - public static class TestConfiguration { - - } - - @Component - @ManagedResource - public static class ManagedEndpoint extends AbstractEndpoint { - - public ManagedEndpoint() { - super("managed"); - } - - @Override - public Boolean invoke() { - return true; - } - - } - - @Configuration - @ManagedResource - public static class NestedInManagedEndpoint { - - @Bean - public Endpoint nested() { - return new Nested(); - } - - class Nested extends AbstractEndpoint { - - Nested() { - super("managed"); - } - - @Override - public Boolean invoke() { - return true; - } - - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMvcIntegrationTests.java deleted file mode 100755 index edd4cd0ba3..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointMvcIntegrationTests.java +++ /dev/null @@ -1,174 +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.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConverters; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; -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.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for MVC {@link Endpoint}s. - * - * @author Dave Syer - */ -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "foo.bar=baz") -@DirtiesContext -@TestPropertySource(properties = "management.security.enabled=false") -public class EndpointMvcIntegrationTests { - - @LocalServerPort - private int port; - - @Autowired - private TestInterceptor interceptor; - - @Test - public void envEndpointNotHidden() throws InterruptedException { - String body = new TestRestTemplate().getForObject( - "http://localhost:" + this.port + "/application/env/foo.bar", - String.class); - assertThat(body).isNotNull().contains("\"baz\""); - assertThat(this.interceptor.invoked()).isTrue(); - } - - @Test - public void healthEndpointNotHidden() throws InterruptedException { - String body = new TestRestTemplate().getForObject( - "http://localhost:" + this.port + "/application/health", String.class); - assertThat(body).isNotNull().contains("status"); - assertThat(this.interceptor.invoked()).isTrue(); - } - - @Configuration - @MinimalWebConfiguration - @Import({ JacksonAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class }) - @RestController - protected static class Application { - - private final List> converters; - - public Application(ObjectProvider>> converters) { - this.converters = converters.getIfAvailable(); - } - - @RequestMapping("/{name}/{env}/{bar}") - public Map master(@PathVariable String name, - @PathVariable String env, @PathVariable String label) { - return Collections.singletonMap("foo", (Object) "bar"); - } - - @RequestMapping("/{name}/{env}") - public Map master(@PathVariable String name, - @PathVariable String env) { - return Collections.singletonMap("foo", (Object) "bar"); - } - - @Bean - @ConditionalOnMissingBean - public HttpMessageConverters messageConverters() { - return new HttpMessageConverters(this.converters == null - ? Collections.>emptyList() : this.converters); - } - - @Bean - public EndpointHandlerMappingCustomizer mappingCustomizer() { - return (mapping) -> mapping.setInterceptors(interceptor()); - } - - @Bean - protected TestInterceptor interceptor() { - return new TestInterceptor(); - } - - } - - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @Import({ ServletWebServerFactoryAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, - WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class, - ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) - protected @interface MinimalWebConfiguration { - - } - - protected static class TestInterceptor extends HandlerInterceptorAdapter { - - private final CountDownLatch latch = new CountDownLatch(1); - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, - Object handler, ModelAndView modelAndView) throws Exception { - this.latch.countDown(); - } - - public boolean invoked() throws InterruptedException { - return this.latch.await(30, TimeUnit.SECONDS); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfigurationTests.java deleted file mode 100755 index 2c2072d3df..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcAutoConfigurationTests.java +++ /dev/null @@ -1,885 +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.io.FileNotFoundException; -import java.net.SocketException; -import java.net.URI; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Vector; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.catalina.Valve; -import org.apache.catalina.valves.AccessLogValve; -import org.apache.http.client.HttpClient; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContextBuilder; -import org.hamcrest.Matcher; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; -import org.springframework.boot.actuate.endpoint.Endpoint; -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.LoggersMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -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.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; -import org.springframework.boot.context.event.ApplicationFailedEvent; -import org.springframework.boot.context.properties.source.ConfigurationPropertySources; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.boot.testsupport.assertj.Matched; -import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer; -import org.springframework.boot.web.context.WebServerInitializedEvent; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; -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.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.stereotype.Controller; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; -import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.startsWith; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link EndpointWebMvcAutoConfiguration}. - * - * @author Phillip Webb - * @author Greg Turnquist - * @author Andy Wilkinson - * @author EddĂș MelĂ©ndez - * @author Ben Hale - */ -public class EndpointWebMvcAutoConfigurationTests { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private final AnnotationConfigServletWebServerApplicationContext applicationContext = new AnnotationConfigServletWebServerApplicationContext(); - - private static ThreadLocal ports = new ThreadLocal<>(); - - @Before - public void setUp() { - Ports values = new Ports(); - ports.set(values); - TestPropertyValues - .of("management.security.enabled=false", "server.servlet.context-path=", - "server.port=" + ports.get().server) - .applyTo(this.applicationContext); - } - - @After - public void cleanUp() throws Exception { - this.applicationContext.close(); - } - - @Test - public void onSamePort() throws Exception { - TestPropertyValues.of("management.security.enabled=false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/application/endpoint", ports.get().server, "endpointoutput"); - assertThat(hasHeader("/application/endpoint", ports.get().server, - "X-Application-Context")).isFalse(); - assertThat(this.applicationContext.containsBean("applicationContextIdFilter")) - .isFalse(); - } - - @Test - public void onSamePortWithHeader() throws Exception { - TestPropertyValues.of("management.add-application-context-header:true") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertThat(hasHeader("/endpoint", ports.get().server, "X-Application-Context")) - .isTrue(); - assertThat(this.applicationContext.containsBean("applicationContextIdFilter")) - .isTrue(); - } - - @Test - public void onDifferentPort() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/application/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - } - - @Test - public void onDifferentPortAndRootContext() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management, - "management.context-path=/").applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - } - - @Test - public void onDifferentPortWithSpecificServer() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(SpecificWebServerConfig.class, RootConfig.class, - DifferentPortConfig.class, EndpointConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/application/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - ServletWebServerFactory parentFactory = this.applicationContext - .getBean(ServletWebServerFactory.class); - ServletWebServerFactory managementFactory = managementContext - .getBean(ServletWebServerFactory.class); - assertThat(parentFactory).isInstanceOf(SpecificServletWebServerFactory.class); - assertThat(managementFactory).isInstanceOf(SpecificServletWebServerFactory.class); - assertThat(managementFactory).isNotSameAs(parentFactory); - } - - @Test - public void onDifferentPortAndContext() throws Exception { - TestPropertyValues - .of("management.port=" + ports.get().management, - "management.context-path=/admin") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/admin/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - } - - @Test - public void onDifferentPortAndMainContext() throws Exception { - TestPropertyValues - .of("server.servlet.context-path=/spring", - "management.port=" + ports.get().management, - "management.context-path=/admin") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/spring/controller", ports.get().server, "controlleroutput"); - assertContent("/admin/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - } - - @Test - public void onDifferentPortWithoutErrorMvcAutoConfiguration() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/error", ports.get().management, null); - } - - @Test - public void onDifferentPortInWebServer() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - ServletContext servletContext = mock(ServletContext.class); - given(servletContext.getInitParameterNames()) - .willReturn(new Vector().elements()); - given(servletContext.getAttributeNames()) - .willReturn(new Vector().elements()); - this.applicationContext.setServletContext(servletContext); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().management, null); - assertContent("/endpoint", ports.get().management, null); - } - - @Test - public void onDifferentPortWithPrimaryFailure() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - ApplicationFailedEvent event = mock(ApplicationFailedEvent.class); - given(event.getApplicationContext()).willReturn(this.applicationContext); - this.applicationContext.publishEvent(event); - assertThat(((ConfigurableApplicationContext) managementContext).isActive()) - .isFalse(); - } - - @Test - public void disabled() throws Exception { - TestPropertyValues.of("management.port=-1").applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/endpoint", ports.get().management, null); - } - - @Test - public void specificPortsViaProperties() throws Exception { - TestPropertyValues - .of("server.port:" + ports.get().server, - "management.port:" + ports.get().management, - "management.security.enabled:false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/application/endpoint", ports.get().management, "endpointoutput"); - } - - @Test - public void managementContextFailureCausesMainContextFailure() throws Exception { - TestPropertyValues - .of("server.port:" + ports.get().server, - "management.port:" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.applicationContext.addApplicationListener( - (ApplicationListener) (event) -> { - if (event.getApplicationContext().getParent() != null) { - throw new RuntimeException(); - } - }); - this.thrown.expect(RuntimeException.class); - this.applicationContext.refresh(); - } - - @Test - public void contextPath() throws Exception { - TestPropertyValues - .of("management.context-path:/test", "management.security.enabled:false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - PropertyPlaceholderAutoConfiguration.class, - JacksonAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/test/endpoint", ports.get().server, "endpointoutput"); - } - - @Test - public void overrideServerProperties() throws Exception { - TestPropertyValues.of("server.displayName:foo").applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - PropertyPlaceholderAutoConfiguration.class, - JacksonAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - ServerProperties serverProperties = this.applicationContext - .getBean(ServerProperties.class); - assertThat(serverProperties.getDisplayName()).isEqualTo("foo"); - } - - @Test - public void portPropertiesOnSamePort() throws Exception { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - new ServerPortInfoApplicationContextInitializer() - .initialize(this.applicationContext); - this.applicationContext.refresh(); - Integer localServerPort = this.applicationContext.getEnvironment() - .getProperty("local.server.port", Integer.class); - Integer localManagementPort = this.applicationContext.getEnvironment() - .getProperty("local.management.port", Integer.class); - assertThat(localServerPort).isNotNull(); - assertThat(localManagementPort).isNotNull(); - assertThat(localServerPort).isEqualTo(localManagementPort); - } - - @Test - public void portPropertiesOnDifferentPort() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - new ServerPortInfoApplicationContextInitializer() - .initialize(this.applicationContext); - this.applicationContext.register(RootConfig.class, DifferentPortConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - Integer localServerPort = this.applicationContext.getEnvironment() - .getProperty("local.server.port", Integer.class); - Integer localManagementPort = this.applicationContext.getEnvironment() - .getProperty("local.management.port", Integer.class); - assertThat(localServerPort).isNotNull(); - assertThat(localManagementPort).isNotNull(); - assertThat(localServerPort).isNotEqualTo(localManagementPort); - } - - @Test - public void singleRequestMappingInfoHandlerMappingBean() throws Exception { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - RequestMappingInfoHandlerMapping mapping = this.applicationContext - .getBean(RequestMappingInfoHandlerMapping.class); - assertThat(mapping).isNotEqualTo(instanceOf(EndpointHandlerMapping.class)); - } - - @Test - public void endpointsDefaultConfiguration() throws Exception { - this.applicationContext.register(LoggingConfig.class, RootConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - this.applicationContext.refresh(); - // /health, /metrics, /loggers, /env, /actuator, /heapdump, /auditevents - // (/shutdown is disabled by default) - assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).hasSize(6); - } - - @Test - public void endpointsAllDisabled() throws Exception { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - TestPropertyValues.of("endpoints.enabled:false").applyTo(this.applicationContext); - this.applicationContext.refresh(); - assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).isEmpty(); - } - - @Test - public void environmentEndpointDisabled() throws Exception { - endpointDisabled("env", EnvironmentMvcEndpoint.class); - } - - @Test - public void environmentEndpointEnabledOverride() throws Exception { - endpointEnabledOverride("env", EnvironmentMvcEndpoint.class); - } - - @Test - public void loggersEndpointDisabled() throws Exception { - endpointDisabled("loggers", LoggersMvcEndpoint.class); - } - - @Test - public void loggersEndpointEnabledOverride() throws Exception { - endpointEnabledOverride("loggers", LoggersMvcEndpoint.class); - } - - @Test - public void metricsEndpointDisabled() throws Exception { - endpointDisabled("metrics", MetricsMvcEndpoint.class); - } - - @Test - public void metricsEndpointEnabledOverride() throws Exception { - endpointEnabledOverride("metrics", MetricsMvcEndpoint.class); - } - - @Test - public void healthEndpointDisabled() throws Exception { - endpointDisabled("health", HealthMvcEndpoint.class); - } - - @Test - public void healthEndpointEnabledOverride() throws Exception { - endpointEnabledOverride("health", HealthMvcEndpoint.class); - } - - @Test - public void shutdownEndpointEnabled() { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - TestPropertyValues.of("endpoints.shutdown.enabled:true") - .applyTo(this.applicationContext); - this.applicationContext.refresh(); - assertThat(this.applicationContext.getBeansOfType(ShutdownMvcEndpoint.class)) - .hasSize(1); - } - - @Test - public void managementSpecificSslUsingDifferentPort() throws Exception { - TestPropertyValues - .of("management.port=" + ports.get().management, - "management.ssl.enabled=true", - "management.ssl.key-store=classpath:test.jks", - "management.ssl.key-password=password") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertContent("/controller", ports.get().server, "controlleroutput"); - assertContent("/endpoint", ports.get().server, null); - assertHttpsContent("/controller", ports.get().management, null); - assertHttpsContent("/application/endpoint", ports.get().management, - "endpointoutput"); - assertHttpsContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - ManagementServerProperties managementServerProperties = this.applicationContext - .getBean(ManagementServerProperties.class); - assertThat(managementServerProperties.getSsl()).isNotNull(); - assertThat(managementServerProperties.getSsl().isEnabled()).isTrue(); - } - - @Test - public void managementSpecificSslUsingSamePortFails() throws Exception { - TestPropertyValues - .of("management.ssl.enabled=true", - "management.ssl.key-store=classpath:test.jks", - "management.ssl.key-password=password") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage("Management-specific SSL cannot be configured as the " - + "management server is not listening on a separate port"); - this.applicationContext.refresh(); - } - - @Test - public void rootManagementContextPathUsingSamePortFails() throws Exception { - TestPropertyValues.of("management.context-path=/") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage("A management context path of '/' requires the" - + " management server to be listening on a separate port"); - this.applicationContext.refresh(); - } - - @Test - public void samePortCanBeUsedWhenManagementSslIsExplicitlyDisabled() - throws Exception { - TestPropertyValues.of("management.ssl.enabled=false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class, - ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - } - - @Test - public void managementServerCanDisableSslWhenUsingADifferentPort() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management, - "server.ssl.enabled=true", "server.ssl.key-store=classpath:test.jks", - "server.ssl.key-password=password", "management.ssl.enabled=false") - .applyTo(this.applicationContext); - this.applicationContext.register(RootConfig.class, EndpointConfig.class, - DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - assertHttpsContent("/controller", ports.get().server, "controlleroutput"); - assertHttpsContent("/endpoint", ports.get().server, null); - assertContent("/controller", ports.get().management, null); - assertContent("/application/endpoint", ports.get().management, "endpointoutput"); - assertContent("/error", ports.get().management, startsWith("{")); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - List interceptors = (List) ReflectionTestUtils.getField( - managementContext.getBean(EndpointHandlerMapping.class), "interceptors"); - assertThat(interceptors).hasSize(2); - ManagementServerProperties managementServerProperties = this.applicationContext - .getBean(ManagementServerProperties.class); - assertThat(managementServerProperties.getSsl()).isNotNull(); - assertThat(managementServerProperties.getSsl().isEnabled()).isFalse(); - } - - @Test - public void tomcatManagementAccessLogUsesCustomPrefix() throws Exception { - TestPropertyValues.of("management.port=" + ports.get().management) - .applyTo(this.applicationContext); - this.applicationContext.register(TomcatWebServerConfig.class, RootConfig.class, - EndpointConfig.class, DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - TestPropertyValues.of("server.tomcat.accesslog.enabled: true") - .applyTo(this.applicationContext); - this.applicationContext.refresh(); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - ServletWebServerFactory factory = managementContext - .getBean(ServletWebServerFactory.class); - assertThat(factory).isInstanceOf(TomcatServletWebServerFactory.class); - AccessLogValve accessLogValve = findAccessLogValve( - ((TomcatServletWebServerFactory) factory)); - assertThat(accessLogValve).isNotNull(); - assertThat(accessLogValve.getPrefix()).isEqualTo("management_access_log"); - } - - @Test - public void undertowManagementAccessLogUsesCustomPrefix() throws Exception { - TestPropertyValues - .of("management.port=" + ports.get().management, - "server.undertow.accesslog.enabled: true") - .applyTo(this.applicationContext); - this.applicationContext.register(UndertowWebServerConfig.class, RootConfig.class, - EndpointConfig.class, DifferentPortConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); - this.applicationContext.refresh(); - ApplicationContext managementContext = this.applicationContext - .getBean(ManagementContextResolver.class).getApplicationContext(); - ServletWebServerFactory factory = managementContext - .getBean(ServletWebServerFactory.class); - assertThat(factory).isInstanceOf(UndertowServletWebServerFactory.class); - assertThat(((UndertowServletWebServerFactory) factory).getAccessLogPrefix()) - .isEqualTo("management_access_log."); - } - - private AccessLogValve findAccessLogValve( - TomcatServletWebServerFactory webServerFactory) { - for (Valve engineValve : webServerFactory.getEngineValves()) { - if (engineValve instanceof AccessLogValve) { - return (AccessLogValve) engineValve; - } - } - return null; - } - - private void endpointDisabled(String name, Class type) { - this.applicationContext.register(RootConfig.class, BaseConfiguration.class, - EndpointWebMvcAutoConfiguration.class); - TestPropertyValues.of(String.format("endpoints.%s.enabled:false", name)) - .applyTo(this.applicationContext); - this.applicationContext.refresh(); - assertThat(this.applicationContext.getBeansOfType(type)).isEmpty(); - } - - private void endpointEnabledOverride(String name, Class type) - throws Exception { - this.applicationContext.register(LoggingConfig.class, RootConfig.class, - BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class); - ConfigurationPropertySources.attach(this.applicationContext.getEnvironment()); - TestPropertyValues - .of("endpoints.enabled:false", - String.format("endpoints.%s.enabled:true", name)) - .applyTo(this.applicationContext); - this.applicationContext.refresh(); - assertThat(this.applicationContext.getBeansOfType(type)).hasSize(1); - } - - private void assertHttpsContent(String url, int port, Object expected) - throws Exception { - assertContent("https", url, port, expected); - } - - private void assertContent(String url, int port, Object expected) throws Exception { - assertContent("http", url, port, expected); - } - - private void assertContent(String scheme, String url, int port, Object expected) - throws Exception { - - SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( - new SSLContextBuilder() - .loadTrustMaterial(null, new TrustSelfSignedStrategy()).build()); - HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory) - .build(); - HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory( - httpClient); - ClientHttpRequest request = requestFactory.createRequest( - new URI(scheme + "://localhost:" + port + url), HttpMethod.GET); - try (ClientHttpResponse response = request.execute()) { - if (HttpStatus.NOT_FOUND.equals(response.getStatusCode())) { - throw new FileNotFoundException(); - } - String actual = StreamUtils.copyToString(response.getBody(), - Charset.forName("UTF-8")); - if (expected instanceof Matcher) { - assertThat(actual).is(Matched.by((Matcher) expected)); - } - else { - assertThat(actual).isEqualTo(expected); - } - } - catch (Exception ex) { - if (expected == null) { - if (SocketException.class.isInstance(ex) - || FileNotFoundException.class.isInstance(ex)) { - return; - } - } - throw ex; - } - } - - public boolean hasHeader(String url, int port, String header) throws Exception { - SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory(); - ClientHttpRequest request = clientHttpRequestFactory - .createRequest(new URI("http://localhost:" + port + url), HttpMethod.GET); - ClientHttpResponse response = request.execute(); - return response.getHeaders().containsKey(header); - } - - private static class Ports { - - int server = 0; - - int management = 0; - - } - - @Configuration - @Import({ PropertyPlaceholderAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class, - JacksonAutoConfiguration.class, EndpointAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - AuditAutoConfiguration.class }) - protected static class BaseConfiguration { - - } - - @Configuration - public static class RootConfig { - - @Bean - public TestController testController() { - return new TestController(); - } - - @Bean - public ApplicationListener serverPortListener() { - return (event) -> { - int port = event.getWebServer().getPort(); - if (event.getApplicationContext().getParent() == null) { - ports.get().server = port; - } - else { - ports.get().management = port; - } - }; - } - - } - - @Configuration - public static class EndpointConfig { - - @Bean - public TestEndpoint testEndpoint() { - return new TestEndpoint(); - } - - } - - @Configuration - public static class LoggingConfig { - - @Bean - public LoggingSystem loggingSystem() { - return LoggingSystem.get(getClass().getClassLoader()); - } - - } - - @Controller - public static class TestController { - - @RequestMapping("/controller") - @ResponseBody - public String requestMappedMethod() { - return "controlleroutput"; - } - - } - - @Configuration - public static class SpecificWebServerConfig { - - @Bean - public SpecificServletWebServerFactory webServerFactory() { - return new SpecificServletWebServerFactory(); - } - - } - - @Configuration - public static class TomcatWebServerConfig { - - @Bean - public TomcatServletWebServerFactory webServerFactory() { - return new TomcatServletWebServerFactory(); - } - - } - - @Configuration - public static class UndertowWebServerConfig { - - @Bean - public UndertowServletWebServerFactory webServerFactory() { - return new UndertowServletWebServerFactory(); - } - - } - - @Configuration - public static class DifferentPortConfig { - - @Bean - public EndpointHandlerMappingCustomizer mappingCustomizer() { - return (mapping) -> mapping.setInterceptors(interceptor()); - } - - @Bean - protected TestInterceptor interceptor() { - return new TestInterceptor(); - } - - protected static class TestInterceptor extends HandlerInterceptorAdapter { - - private int count = 0; - - @Override - public void postHandle(HttpServletRequest request, - HttpServletResponse response, Object handler, - ModelAndView modelAndView) throws Exception { - this.count++; - } - - public int getCount() { - return this.count; - } - - } - - } - - public static class TestEndpoint implements MvcEndpoint { - - @RequestMapping - @ResponseBody - public String invoke() { - return "endpointoutput"; - } - - @Override - public String getPath() { - return "/endpoint"; - } - - @Override - @SuppressWarnings("rawtypes") - public Class getEndpointType() { - return Endpoint.class; - } - - } - - private static class SpecificServletWebServerFactory - extends TomcatServletWebServerFactory { - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfigurationTests.java deleted file mode 100644 index 2bf28efddb..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointWebMvcManagementContextConfigurationTests.java +++ /dev/null @@ -1,92 +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.List; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpointSecurityInterceptor; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.mock.web.MockServletContext; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointWebMvcManagementContextConfiguration}. - * - * @author Madhura Bhave - */ -public class EndpointWebMvcManagementContextConfigurationTests { - - private AnnotationConfigWebApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(SecurityAutoConfiguration.class, - WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - RestTemplateAutoConfiguration.class, - EndpointWebMvcManagementContextConfiguration.class); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void endpointHandlerMapping() throws Exception { - TestPropertyValues - .of("management.security.enabled=false", - "management.security.roles=my-role,your-role") - .applyTo(this.context); - this.context.refresh(); - EndpointHandlerMapping mapping = this.context.getBean("endpointHandlerMapping", - EndpointHandlerMapping.class); - assertThat(mapping.getPrefix()).isEqualTo("/application"); - MvcEndpointSecurityInterceptor securityInterceptor = (MvcEndpointSecurityInterceptor) ReflectionTestUtils - .getField(mapping, "securityInterceptor"); - Object secure = ReflectionTestUtils.getField(securityInterceptor, "secure"); - List roles = getRoles(securityInterceptor); - assertThat(secure).isEqualTo(false); - assertThat(roles).containsExactly("my-role", "your-role"); - } - - @SuppressWarnings("unchecked") - private List getRoles(MvcEndpointSecurityInterceptor securityInterceptor) { - return (List) ReflectionTestUtils.getField(securityInterceptor, "roles"); - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelectorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelectorTests.java deleted file mode 100644 index 0436f74794..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ManagementContextConfigurationsImportSelectorTests.java +++ /dev/null @@ -1,74 +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.Arrays; -import java.util.List; - -import org.junit.Test; - -import org.springframework.core.annotation.Order; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ManagementContextConfigurationsImportSelector}. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -public class ManagementContextConfigurationsImportSelectorTests { - - @Test - public void selectImportsShouldOrderResult() throws Exception { - String[] imports = new TestManagementContextConfigurationsImportSelector() - .selectImports(null); - assertThat(imports).containsExactly(A.class.getName(), B.class.getName(), - C.class.getName(), D.class.getName()); - } - - private static class TestManagementContextConfigurationsImportSelector - extends ManagementContextConfigurationsImportSelector { - - @Override - protected List loadFactoryNames() { - return Arrays.asList(C.class.getName(), A.class.getName(), D.class.getName(), - B.class.getName()); - } - - } - - @Order(1) - private static class A { - - } - - @Order(2) - private static class B { - - } - - @Order(3) - private static class C { - - } - - static class D { - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/MvcEndpointPathConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/MvcEndpointPathConfigurationTests.java deleted file mode 100644 index a44c7aa142..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/MvcEndpointPathConfigurationTests.java +++ /dev/null @@ -1,173 +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 liquibase.integration.spring.SpringLiquibase; -import org.flywaydb.core.Flyway; -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration; -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.FlywayEndpoint; -import org.springframework.boot.actuate.endpoint.InfoEndpoint; -import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint; -import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.boot.actuate.endpoint.TraceEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; -import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -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.MvcEndpoints; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockServletContext; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for configuring the path of an MVC endpoint. - * - * @author Andy Wilkinson - * @author Ben Hale - */ -@RunWith(Parameterized.class) -public class MvcEndpointPathConfigurationTests { - - private final String endpointName; - - private final Class endpointClass; - - private AnnotationConfigWebApplicationContext context; - - @After - public void cleanUp() { - if (this.context != null) { - this.context.close(); - } - } - - @Parameters(name = "{0}") - public static Object[] parameters() { - return new Object[] { - new Object[] { "auditevents", AuditEventsMvcEndpoint.class }, - new Object[] { "autoconfig", AutoConfigurationReportEndpoint.class }, - new Object[] { "beans", BeansEndpoint.class }, - new Object[] { "configprops", - ConfigurationPropertiesReportEndpoint.class }, - new Object[] { "dump", DumpEndpoint.class }, - new Object[] { "env", EnvironmentMvcEndpoint.class }, - new Object[] { "flyway", FlywayEndpoint.class }, - new Object[] { "health", HealthMvcEndpoint.class }, - new Object[] { "info", InfoEndpoint.class }, - new Object[] { "liquibase", LiquibaseEndpoint.class }, - new Object[] { "logfile", LogFileMvcEndpoint.class }, - new Object[] { "loggers", LoggersMvcEndpoint.class }, - new Object[] { "mappings", RequestMappingEndpoint.class }, - new Object[] { "metrics", MetricsMvcEndpoint.class }, - new Object[] { "shutdown", ShutdownEndpoint.class }, - new Object[] { "trace", TraceEndpoint.class } }; - } - - public MvcEndpointPathConfigurationTests(String endpointName, - Class endpointClass) { - this.endpointName = endpointName; - this.endpointClass = endpointClass; - } - - @Test - public void pathCanBeConfigured() { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(TestConfiguration.class); - this.context.setServletContext(new MockServletContext()); - TestPropertyValues - .of("endpoints." + this.endpointName + ".path" + ":/custom/path", - "endpoints." + this.endpointName + ".enabled:true", - "logging.file:target/test.log") - .applyTo(this.context); - this.context.refresh(); - assertThat(getConfiguredPath()).isEqualTo("/custom/path"); - } - - private String getConfiguredPath() { - if (MvcEndpoint.class.isAssignableFrom(this.endpointClass)) { - return ((MvcEndpoint) this.context.getBean(this.endpointClass)).getPath(); - } - for (MvcEndpoint endpoint : this.context.getBean(MvcEndpoints.class) - .getEndpoints()) { - if (endpoint instanceof EndpointMvcAdapter && this.endpointClass - .isInstance(((EndpointMvcAdapter) endpoint).getDelegate())) { - return ((EndpointMvcAdapter) endpoint).getPath(); - } - } - throw new IllegalStateException( - "Could not get configured path for " + this.endpointClass); - } - - @Configuration - @ImportAutoConfiguration({ EndpointAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, AuditAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, - JolokiaManagementContextConfiguration.class }) - - protected static class TestConfiguration { - - @Bean - public ConditionEvaluationReport conditionEvaluationReport( - ConfigurableListableBeanFactory beanFactory) { - return ConditionEvaluationReport.get(beanFactory); - } - - @Bean - LoggingSystem loggingSystem() { - return LoggingSystem.get(getClass().getClassLoader()); - } - - @Bean - public FlywayEndpoint flyway() { - return new FlywayEndpoint(Collections.singletonMap("flyway", new Flyway())); - } - - @Bean - public LiquibaseEndpoint liquibase() { - return new LiquibaseEndpoint(new SpringLiquibase()); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactoryTests.java new file mode 100644 index 0000000000..2f715257f6 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/CachingConfigurationFactoryTests.java @@ -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.endpoint.infrastructure; + +import org.junit.Test; + +import org.springframework.boot.endpoint.CachingConfiguration; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * Tests for {@link CachingConfigurationFactory}. + * + * @author Stephane Nicoll + */ +public class CachingConfigurationFactoryTests { + + private final MockEnvironment environment = new MockEnvironment(); + + private final CachingConfigurationFactory factory = new CachingConfigurationFactory( + this.environment); + + @Test + public void defaultConfiguration() { + CachingConfiguration configuration = this.factory.apply("test"); + assertThat(configuration).isNotNull(); + assertThat(configuration.getTimeToLive()).isEqualTo(0); + } + + @Test + public void userConfiguration() { + this.environment.setProperty("endpoints.test.cache.time-to-live", "500"); + CachingConfiguration configuration = this.factory.apply("test"); + assertThat(configuration).isNotNull(); + assertThat(configuration.getTimeToLive()).isEqualTo(500); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointInfrastructureAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointInfrastructureAutoConfigurationTests.java new file mode 100644 index 0000000000..a5ecb117c5 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/JmxEndpointInfrastructureAutoConfigurationTests.java @@ -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 javax.management.InstanceNotFoundException; +import javax.management.IntrospectionException; +import javax.management.MBeanInfo; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.ReflectionException; + +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link EndpointInfrastructureAutoConfiguration} with JMX. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +public class JmxEndpointInfrastructureAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, + EndpointAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class)); + + @Test + public void jmxEndpointsAreExposed() { + this.contextRunner.run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + checkEndpointMBeans(mBeanServer, + new String[] { "autoconfig", "beans", "configprops", "env", "health", + "info", "mappings", "metrics", "threaddump", "trace" }, + new String[] { "shutdown" }); + }); + } + + @Test + public void jmxEndpointsCanBeDisabled() { + this.contextRunner.withPropertyValues("endpoints.all.jmx.enabled:false") + .run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + checkEndpointMBeans(mBeanServer, new String[0], + new String[] { "autoconfig", "beans", "configprops", "env", + "health", "info", "mappings", "metrics", "shutdown", + "threaddump", "trace" }); + + }); + } + + @Test + public void singleJmxEndpointCanBeEnabled() { + this.contextRunner.withPropertyValues("endpoints.all.jmx.enabled=false", + "endpoints.beans.jmx.enabled=true").run((context) -> { + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + checkEndpointMBeans(mBeanServer, new String[] { "beans" }, + new String[] { "autoconfig", "configprops", "env", "health", + "info", "mappings", "metrics", "shutdown", + "threaddump", "trace" }); + }); + } + + private void checkEndpointMBeans(MBeanServer mBeanServer, String[] enabledEndpoints, + String[] disabledEndpoints) { + for (String enabledEndpoint : enabledEndpoints) { + assertThat(isRegistered(mBeanServer, getDefaultObjectName(enabledEndpoint))) + .as(String.format("Endpoint %s", enabledEndpoint)).isTrue(); + } + for (String disabledEndpoint : disabledEndpoints) { + assertThat(isRegistered(mBeanServer, getDefaultObjectName(disabledEndpoint))) + .as(String.format("Endpoint %s", disabledEndpoint)).isFalse(); + } + } + + private boolean isRegistered(MBeanServer mBeanServer, ObjectName objectName) { + try { + getMBeanInfo(mBeanServer, objectName); + return true; + } + catch (InstanceNotFoundException e) { + return false; + } + } + + private MBeanInfo getMBeanInfo(MBeanServer mBeanServer, ObjectName objectName) + throws InstanceNotFoundException { + try { + return mBeanServer.getMBeanInfo(objectName); + } + catch (ReflectionException | IntrospectionException ex) { + throw new IllegalStateException( + "Failed to retrieve MBeanInfo for ObjectName " + objectName, ex); + } + } + + private ObjectName getDefaultObjectName(String endpointId) { + return getObjectName("org.springframework.boot", endpointId); + } + + private ObjectName getObjectName(String domain, String endpointId) { + try { + return new ObjectName(String.format("%s:type=Endpoint,name=%s", domain, + StringUtils.capitalize(endpointId))); + } + catch (MalformedObjectNameException ex) { + throw new IllegalStateException("Invalid object name", ex); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebMvcEndpointInfrastructureAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebMvcEndpointInfrastructureAutoConfigurationTests.java new file mode 100644 index 0000000000..f3215752a8 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/infrastructure/WebMvcEndpointInfrastructureAutoConfigurationTests.java @@ -0,0 +1,153 @@ +/* + * 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.apache.http.HttpStatus; +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +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.test.context.runner.WebApplicationContextRunner; +import org.springframework.http.HttpMethod; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; + +/** + * Tests for {@link EndpointInfrastructureAutoConfiguration} with Web MVC. + * + * @author Stephane Nicoll + */ +public class WebMvcEndpointInfrastructureAutoConfigurationTests { + + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + ServletWebServerFactoryAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, + JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class)); + + @Test + public void webEndpointsAreExposed() { + this.contextRunner.run((context) -> { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/autoconfig")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/beans")).isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/configprops")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/env")).isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/health")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/info")).isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/mappings")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/metrics")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.POST, "/application/shutdown")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/threaddump")) + .isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/trace")).isTrue(); + }); + } + + @Test + public void webEndpointsCanBeDisabled() { + WebApplicationContextRunner contextRunner = this.contextRunner + .withPropertyValues("endpoints.all.web.enabled=false"); + contextRunner.run((context) -> { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/autoconfig")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/beans")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/configprops")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/env")).isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/health")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/info")).isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/mappings")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/metrics")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.POST, "/application/shutdown")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/threaddump")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/trace")) + .isFalse(); + }); + } + + @Test + public void singleWebEndpointCanBeEnabled() { + WebApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues( + "endpoints.all.web.enabled=false", "endpoints.beans.web.enabled=true"); + contextRunner.run((context) -> { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/autoconfig")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/beans")).isTrue(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/configprops")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/env")).isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/health")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/info")).isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/mappings")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/metrics")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.POST, "/application/shutdown")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/threaddump")) + .isFalse(); + assertThat(isExposed(mockMvc, HttpMethod.GET, "/application/trace")) + .isFalse(); + }); + } + + private boolean isExposed(MockMvc mockMvc, HttpMethod method, String path) + throws Exception { + MvcResult mvcResult = mockMvc.perform(request(method, path)).andReturn(); + int status = mvcResult.getResponse().getStatus(); + if (status == HttpStatus.SC_OK) { + return true; + } + else if (status == HttpStatus.SC_NOT_FOUND) { + return false; + } + throw new IllegalStateException(String + .format("Unexpected %s HTTP status for " + "endpoint %s", status, path)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java new file mode 100644 index 0000000000..1db7a43b59 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java @@ -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.jmx; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.boot.actuate.endpoint.jmx.AuditEventsJmxEndpointExtension; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link JmxEndpointAutoConfiguration}. + * + * @author Andy Wilkinson + */ +public class JmxEndpointAutoConfigurationTests { + + @Test + public void auditEventsJmxEndpointExtensionIsAutoConfigured() { + jmxExtensionIsAutoConfigured(AuditEventsJmxEndpointExtension.class, + AuditEventsEndpointConfiguration.class); + } + + @Test + public void auditEventsJmxEndpointExtensionCanBeDisabled() { + jmxExtensionCanBeDisabled(AuditEventsJmxEndpointExtension.class, "auditevents", + AuditEventsEndpointConfiguration.class); + } + + private void jmxExtensionIsAutoConfigured(Class jmxExtension, Class... config) { + contextRunner().withUserConfiguration(config) + .run((context) -> assertThat(context).hasSingleBean(jmxExtension)); + } + + private void jmxExtensionCanBeDisabled(Class jmxExtension, String id, + Class... config) { + contextRunner().withPropertyValues("endpoints." + id + ".enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(jmxExtension)); + } + + private ApplicationContextRunner contextRunner() { + return new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(JmxEndpointAutoConfiguration.class)); + } + + @Configuration + static class AuditEventsEndpointConfiguration { + + @Bean + public AuditEventsEndpoint auditEventsEndpoint() { + return new AuditEventsEndpoint(mock(AuditEventRepository.class)); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProviderTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProviderTests.java new file mode 100644 index 0000000000..206cae6167 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/support/EndpointEnablementProviderTests.java @@ -0,0 +1,337 @@ +/* + * 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.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.endpoint.EndpointType; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.util.ObjectUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link EndpointEnablementProvider}. + * + * @author Stephane Nicoll + */ +public class EndpointEnablementProviderTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void cannotDetermineEnablementWithEmptyEndpoint() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Endpoint id must have a value"); + determineEnablement(" ", true); + } + + @Test + public void cannotDetermineEnablementOfEndpointAll() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Endpoint id 'all' is a reserved value and cannot " + + "be used by an endpoint"); + determineEnablement("all", true); + } + + @Test + public void generalEnabledByDefault() { + validate(determineEnablement("foo", true), true, + "endpoint 'foo' is enabled by default"); + } + + @Test + public void generalDisabledViaSpecificProperty() { + validate(determineEnablement("foo", true, "endpoints.foo.enabled=false"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void generalDisabledViaGeneralProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false"), false, + "found property endpoints.all.enabled"); + } + + @Test + public void generalEnabledOverrideViaSpecificProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false", + "endpoints.foo.enabled=true"), true, + "found property endpoints.foo.enabled"); + } + + @Test + public void generalEnabledOverrideViaSpecificWebProperty() { + validate(determineEnablement("foo", true, "endpoints.foo.enabled=false", + "endpoints.foo.web.enabled=true"), true, + "found property endpoints.foo.web.enabled"); + } + + @Test + public void generalEnabledOverrideViaSpecificJmxProperty() { + validate(determineEnablement("foo", true, "endpoints.foo.enabled=false", + "endpoints.foo.jmx.enabled=true"), true, + "found property endpoints.foo.jmx.enabled"); + } + + @Test + public void generalEnabledOverrideViaSpecificAnyProperty() { + validate(determineEnablement("foo", true, "endpoints.foo.enabled=false", + "endpoints.foo.web.enabled=false", "endpoints.foo.jmx.enabled=true"), + true, "found property endpoints.foo.jmx.enabled"); + } + + @Test + public void generalEnabledOverrideViaGeneralWebProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false", + "endpoints.all.web.enabled=true"), true, + "found property endpoints.all.web.enabled"); + } + + @Test + public void generalEnabledOverrideViaGeneralJmxProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false", + "endpoints.all.jmx.enabled=true"), true, + "found property endpoints.all.jmx.enabled"); + } + + @Test + public void generalEnabledOverrideViaGeneralAnyProperty() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=false", + "endpoints.all.web.enabled=false", "endpoints.all.jmx.enabled=true"), + true, "found property endpoints.all.jmx.enabled"); + } + + @Test + public void generalDisabledEvenWithEnabledGeneralProperties() { + validate(determineEnablement("foo", true, "endpoints.all.enabled=true", + "endpoints.all.web.enabled=true", "endpoints.all.jmx.enabled=true", + "endpoints.foo.enabled=false"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void generalDisabledByDefaultWithAnnotationFlag() { + validate(determineEnablement("bar", false), false, + "endpoint 'bar' is disabled by default"); + } + + @Test + public void generalDisabledByDefaultWithAnnotationFlagEvenWithGeneralProperty() { + validate(determineEnablement("bar", false, "endpoints.all.enabled=true"), false, + "endpoint 'bar' is disabled by default"); + } + + @Test + public void generalDisabledByDefaultWithAnnotationFlagEvenWithGeneralWebProperty() { + validate(determineEnablement("bar", false, "endpoints.all.web.enabled=true"), + false, "endpoint 'bar' is disabled by default"); + } + + @Test + public void generalDisabledByDefaultWithAnnotationFlagEvenWithGeneralJmxProperty() { + validate(determineEnablement("bar", false, "endpoints.all.jmx.enabled=true"), + false, "endpoint 'bar' is disabled by default"); + } + + @Test + public void generalEnabledOverrideWithAndAnnotationFlagAndSpecificProperty() { + validate(determineEnablement("bar", false, "endpoints.bar.enabled=true"), + true, "found property endpoints.bar.enabled"); + } + + @Test + public void generalEnabledOverrideWithAndAnnotationFlagAndSpecificWebProperty() { + validate(determineEnablement("bar", false, "endpoints.bar.web.enabled=true"), + true, "found property endpoints.bar.web.enabled"); + } + + @Test + public void generalEnabledOverrideWithAndAnnotationFlagAndSpecificJmxProperty() { + validate(determineEnablement("bar", false, "endpoints.bar.jmx.enabled=true"), + true, "found property endpoints.bar.jmx.enabled"); + } + + @Test + public void generalEnabledOverrideWithAndAnnotationFlagAndAnyProperty() { + validate(determineEnablement("bar", false, "endpoints.bar.web.enabled=false", + "endpoints.bar.jmx.enabled=true"), true, + "found property endpoints.bar.jmx.enabled"); + } + + + @Test + public void specificEnabledByDefault() { + validate(determineEnablement("foo", true, EndpointType.WEB), true, + "endpoint 'foo' (web) is enabled by default"); + } + + @Test + public void specificDisabledViaEndpointProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.enabled=false"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void specificDisabledViaTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.web.enabled=false"), false, + "found property endpoints.foo.web.enabled"); + } + + @Test + public void specificNotDisabledViaUnrelatedTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.jmx.enabled=false"), true, + "endpoint 'foo' (web) is enabled by default"); + } + + @Test + public void specificDisabledViaGeneralProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=false"), false, + "found property endpoints.all.enabled"); + } + + @Test + public void specificEnabledOverrideViaEndpointProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=false", "endpoints.foo.enabled=true"), true, + "found property endpoints.foo.enabled"); + } + + @Test + public void specificEnabledOverrideViaTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.enabled=false", "endpoints.foo.web.enabled=true"), true, + "found property endpoints.foo.web.enabled"); + } + + @Test + public void specificEnabledOverrideHasNotEffectWithUnrelatedTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.foo.enabled=false", "endpoints.foo.jmx.enabled=true"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void specificEnabledOverrideViaGeneralWebProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=false", "endpoints.all.web.enabled=true"), true, + "found property endpoints.all.web.enabled"); + } + + @Test + public void specificEnabledOverrideHasNoEffectWithUnrelatedTechProperty() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=false", "endpoints.all.jmx.enabled=true"), false, + "found property endpoints.all.enabled"); + } + + @Test + public void specificDisabledWithEndpointPropertyEvenWithEnabledGeneralProperties() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=true", "endpoints.all.web.enabled=true", + "endpoints.all.jmx.enabled=true", "endpoints.foo.enabled=false"), false, + "found property endpoints.foo.enabled"); + } + + @Test + public void specificDisabledWithTechPropertyEvenWithEnabledGeneralProperties() { + validate(determineEnablement("foo", true, EndpointType.WEB, + "endpoints.all.enabled=true", "endpoints.all.web.enabled=true", + "endpoints.all.jmx.enabled=true", "endpoints.foo.enabled=true", + "endpoints.foo.web.enabled=false"), false, + "found property endpoints.foo.web.enabled"); + } + + @Test + public void specificDisabledByDefaultWithAnnotationFlag() { + validate(determineEnablement("bar", false, EndpointType.WEB), false, + "endpoint 'bar' (web) is disabled by default"); + } + + @Test + public void specificDisabledByDefaultWithAnnotationFlagEvenWithGeneralProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.all.enabled=true"), false, + "endpoint 'bar' (web) is disabled by default"); + } + + @Test + public void specificDisabledByDefaultWithAnnotationFlagEvenWithGeneralWebProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.all.web.enabled=true"), false, + "endpoint 'bar' (web) is disabled by default"); + } + + @Test + public void specificDisabledByDefaultWithAnnotationFlagEvenWithGeneralJmxProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.all.jmx.enabled=true"), false, + "endpoint 'bar' (web) is disabled by default"); + } + + @Test + public void specificEnabledOverrideWithAndAnnotationFlagAndEndpointProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.bar.enabled=true"), true, + "found property endpoints.bar.enabled"); + } + + @Test + public void specificEnabledOverrideWithAndAnnotationFlagAndTechProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.bar.web.enabled=true"), true, + "found property endpoints.bar.web.enabled"); + } + + @Test + public void specificEnabledOverrideWithAndAnnotationFlagHasNoEffectWithUnrelatedTechProperty() { + validate(determineEnablement("bar", false, EndpointType.WEB, + "endpoints.bar.jmx.enabled=true"), false, + "endpoint 'bar' (web) is disabled by default"); + } + + + private void validate(EndpointEnablement enablement, boolean enabled, + String... messages) { + assertThat(enablement).isNotNull(); + assertThat(enablement.isEnabled()).isEqualTo(enabled); + if (!ObjectUtils.isEmpty(messages)) { + assertThat(enablement.getReason()).contains(messages); + } + } + + private EndpointEnablement determineEnablement(String id, boolean enabledByDefault, + String... environment) { + return determineEnablement(id, enabledByDefault, null, environment); + } + + private EndpointEnablement determineEnablement(String id, boolean enabledByDefault, + EndpointType type, String... environment) { + MockEnvironment env = new MockEnvironment(); + TestPropertyValues.of(environment).applyTo(env); + EndpointEnablementProvider provider = new EndpointEnablementProvider(env); + return provider.getEndpointEnablement(id, enabledByDefault, type); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java new file mode 100644 index 0000000000..c12f23587d --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java @@ -0,0 +1,168 @@ +/* + * 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 java.util.Collections; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEventRepository; +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.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link WebEndpointManagementContextConfiguration}. + * + * @author Andy Wilkinson + */ +public class WebEndpointManagementContextConfigurationTests { + + @Test + public void heapDumpWebEndpointIsAutoConfigured() { + beanIsAutoConfigured(HeapDumpWebEndpoint.class); + } + + @Test + public void heapDumpWebEndpointCanBeDisabled() { + beanIsNotAutoConfiguredWhenEndpointIsDisabled(HeapDumpWebEndpoint.class, + "heapdump"); + } + + @Test + public void healthWebEndpointExtensionIsAutoConfigured() { + beanIsAutoConfigured(HealthWebEndpointExtension.class, + HealthEndpointConfiguration.class); + } + + @Test + public void healthStatusMappingCanBeCustomized() { + ApplicationContextRunner contextRunner = contextRunner() + .withPropertyValues("endpoints.health.mapping.CUSTOM=500") + .withUserConfiguration(HealthEndpointConfiguration.class); + contextRunner.run((context) -> { + HealthWebEndpointExtension extension = context + .getBean(HealthWebEndpointExtension.class); + @SuppressWarnings("unchecked") + Map statusMappings = (Map) ReflectionTestUtils + .getField(extension, "statusMapping"); + assertThat(statusMappings).containsEntry("DOWN", 503); + assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503); + assertThat(statusMappings).containsEntry("CUSTOM", 500); + }); + } + + @Test + public void healthWebEndpointExtensionCanBeDisabled() { + beanIsNotAutoConfiguredWhenEndpointIsDisabled(HealthWebEndpointExtension.class, + "health", HealthEndpointConfiguration.class); + } + + @Test + public void auditEventsWebEndpointExtensionIsAutoConfigured() { + beanIsAutoConfigured(AuditEventsWebEndpointExtension.class, + AuditEventsEndpointConfiguration.class); + } + + @Test + public void auditEventsWebEndpointExtensionCanBeDisabled() { + beanIsNotAutoConfiguredWhenEndpointIsDisabled( + AuditEventsWebEndpointExtension.class, "auditevents", + AuditEventsEndpointConfiguration.class); + } + + @Test + public void logFileWebEndpointIsAutoConfiguredWhenLoggingFileIsSet() { + contextRunner().withPropertyValues("logging.file:test.log").run( + (context) -> assertThat(context.getBeansOfType(LogFileWebEndpoint.class)) + .hasSize(1)); + } + + @Test + public void logFileWebEndpointIsAutoConfiguredWhenLoggingPathIsSet() { + contextRunner().withPropertyValues("logging.path:test/logs").run( + (context) -> assertThat(context.getBeansOfType(LogFileWebEndpoint.class)) + .hasSize(1)); + } + + @Test + public void logFileWebEndpointIsAutoConfiguredWhenExternalFileIsSet() { + contextRunner().withPropertyValues("endpoints.logfile.external-file:external.log") + .run((context) -> assertThat( + context.getBeansOfType(LogFileWebEndpoint.class)).hasSize(1)); + } + + @Test + public void logFileWebEndpointCanBeDisabled() { + contextRunner() + .withPropertyValues("logging.file:test.log", + "endpoints.logfile.enabled:false") + .run((context) -> assertThat(context) + .hasSingleBean(LogFileWebEndpoint.class)); + } + + private void beanIsAutoConfigured(Class beanType, Class... config) { + contextRunner().withUserConfiguration(config) + .run((context) -> assertThat(context).hasSingleBean(beanType)); + } + + private void beanIsNotAutoConfiguredWhenEndpointIsDisabled(Class webExtension, + String id, Class... config) { + contextRunner().withPropertyValues("endpoints." + id + ".enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(webExtension)); + } + + private ApplicationContextRunner contextRunner() { + return new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(WebEndpointManagementContextConfiguration.class)); + } + + @Configuration + static class HealthEndpointConfiguration { + + @Bean + public HealthEndpoint healthEndpoint() { + return new HealthEndpoint(new OrderedHealthAggregator(), + Collections.emptyMap()); + } + + } + + @Configuration + static class AuditEventsEndpointConfiguration { + + @Bean + public AuditEventsEndpoint auditEventsEndpoint() { + return new AuditEventsEndpoint(mock(AuditEventRepository.class)); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointAutoConfigurationTests.java deleted file mode 100644 index f2dfe150d9..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthMvcEndpointAutoConfigurationTests.java +++ /dev/null @@ -1,172 +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.health; - -import java.security.Principal; -import java.util.Arrays; - -import javax.servlet.http.HttpServletRequest; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -import org.springframework.boot.actuate.health.AbstractHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Health.Builder; -import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointWebMvcAutoConfiguration} of the {@link HealthMvcEndpoint}. - * - * @author Dave Syer - * @author Andy Wilkinson - */ -public class HealthMvcEndpointAutoConfigurationTests { - - private AnnotationConfigWebApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void testSecureByDefault() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestConfiguration.class); - this.context.refresh(); - MockHttpServletRequest request = new MockHttpServletRequest(); - Health health = (Health) this.context.getBean(HealthMvcEndpoint.class) - .invoke(request, null); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails().get("foo")).isNull(); - } - - @Test - public void testNotSecured() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestConfiguration.class); - TestPropertyValues.of("management.security.enabled=false").applyTo(this.context); - this.context.refresh(); - Health health = (Health) this.context.getBean(HealthMvcEndpoint.class) - .invoke(null, null); - assertThat(health.getStatus()).isEqualTo(Status.UP); - Health map = (Health) health.getDetails().get("test"); - assertThat(map.getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void testSetRoles() throws Exception { - // gh-8314 - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestConfiguration.class); - TestPropertyValues.of("management.security.roles[0]=super").applyTo(this.context); - this.context.refresh(); - HealthMvcEndpoint health = this.context.getBean(HealthMvcEndpoint.class); - assertThat(ReflectionTestUtils.getField(health, "roles")) - .isEqualTo(Arrays.asList("super")); - } - - @Test - public void endpointConditionalOnMissingBean() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestConfiguration.class, - TestHealthMvcEndpointConfiguration.class); - this.context.refresh(); - MockHttpServletRequest request = new MockHttpServletRequest(); - Health health = (Health) this.context.getBean(HealthMvcEndpoint.class) - .invoke(request, null); - assertThat(health.getDetails()).isNotEmpty(); - } - - @Configuration - @ImportAutoConfiguration({ SecurityAutoConfiguration.class, - JacksonAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, AuditAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class }) - static class TestConfiguration { - - @Bean - public TestHealthIndicator testHealthIndicator() { - return new TestHealthIndicator(); - } - - } - - @Configuration - @ImportAutoConfiguration({ SecurityAutoConfiguration.class, - JacksonAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, AuditAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class }) - static class TestHealthMvcEndpointConfiguration { - - @Bean - public HealthMvcEndpoint endpoint(HealthEndpoint endpoint) { - return new TestHealthMvcEndpoint(endpoint); - } - - } - - static class TestHealthMvcEndpoint extends HealthMvcEndpoint { - - TestHealthMvcEndpoint(HealthEndpoint delegate) { - super(delegate); - } - - @Override - protected boolean exposeHealthDetails(HttpServletRequest request, - Principal principal) { - return true; - } - - } - - static class TestHealthIndicator extends AbstractHealthIndicator { - - @Override - protected void doHealthCheck(Builder builder) throws Exception { - builder.up().withDetail("foo", "bar"); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationIntegrationTests.java index 075feb1ca7..47fda28053 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationIntegrationTests.java @@ -26,7 +26,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; @@ -99,8 +101,9 @@ public class JolokiaManagementContextConfigurationIntegrationTests { @MinimalWebConfiguration @Import({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, - JolokiaManagementContextConfiguration.class }) + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class }) protected static class Application { } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java index 136f9befa5..97eebcd637 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/jolokia/JolokiaManagementContextConfigurationTests.java @@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.jolokia; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; @@ -39,7 +39,7 @@ public class JolokiaManagementContextConfigurationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration( - AutoConfigurations.of(EndpointWebMvcAutoConfiguration.class, + AutoConfigurations.of(ManagementContextAutoConfiguration.class, JolokiaManagementContextConfiguration.class)); @Test diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/web/ManagementWebSecurityAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/web/ManagementWebSecurityAutoConfigurationTests.java index 559258a607..fe1f46169a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/web/ManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/web/ManagementWebSecurityAutoConfigurationTests.java @@ -27,7 +27,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -88,7 +88,7 @@ public class ManagementWebSecurityAutoConfigurationTests { ManagementWebSecurityAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, + EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class); TestPropertyValues.of("security.basic.enabled:false").applyTo(this.context); this.context.refresh(); @@ -195,7 +195,7 @@ public class ManagementWebSecurityAutoConfigurationTests { ManagementWebSecurityAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, + EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class, WebMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class); this.context.refresh(); @@ -236,7 +236,7 @@ public class ManagementWebSecurityAutoConfigurationTests { @ImportAutoConfiguration({ SecurityAutoConfiguration.class, WebMvcAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, + EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class, FallbackWebSecurityAutoConfiguration.class }) static class WebConfiguration { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java index 7dfeb37ffb..ef9a65b874 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java @@ -16,41 +16,6 @@ package org.springframework.boot.actuate.cloudfoundry; -import java.util.Arrays; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryActuatorAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcManagementContextConfiguration; -import org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer; -import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.context.properties.source.ConfigurationPropertySources; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.cors.CorsConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - /** * Tests for {@link CloudFoundryActuatorAutoConfiguration}. * @@ -58,146 +23,148 @@ import static org.mockito.Mockito.verify; */ public class CloudFoundryActuatorAutoConfigurationTests { - private AnnotationConfigWebApplicationContext context; + // TODO CloudFoundry support - @Before - public void setup() { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(SecurityAutoConfiguration.class, - WebMvcAutoConfiguration.class, - ManagementWebSecurityAutoConfiguration.class, - JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - RestTemplateAutoConfiguration.class, - EndpointWebMvcManagementContextConfiguration.class, - CloudFoundryActuatorAutoConfiguration.class); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void cloudFoundryPlatformActive() throws Exception { - CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); - assertThat(handlerMapping.getPrefix()).isEqualTo("/cloudfoundryapplication"); - CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils - .getField(handlerMapping, "corsConfiguration"); - assertThat(corsConfiguration.getAllowedOrigins()).contains("*"); - assertThat(corsConfiguration.getAllowedMethods()).containsAll( - Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name())); - assertThat(corsConfiguration.getAllowedHeaders()).containsAll( - Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type")); - } - - @Test - public void cloudFoundryPlatformActiveSetsApplicationId() throws Exception { - CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, - "securityInterceptor"); - String applicationId = (String) ReflectionTestUtils.getField(interceptor, - "applicationId"); - assertThat(applicationId).isEqualTo("my-app-id"); - } - - @Test - public void cloudFoundryPlatformActiveSetsCloudControllerUrl() throws Exception { - CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, - "securityInterceptor"); - Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, - "cloudFoundrySecurityService"); - String cloudControllerUrl = (String) ReflectionTestUtils - .getField(interceptorSecurityService, "cloudControllerUrl"); - assertThat(cloudControllerUrl).isEqualTo("http://my-cloud-controller.com"); - } - - @Test - public void skipSslValidation() throws Exception { - TestPropertyValues.of("management.cloudfoundry.skipSslValidation:true") - .applyTo(this.context); - ConfigurationPropertySources.attach(this.context.getEnvironment()); - this.context.refresh(); - CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, - "securityInterceptor"); - Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, - "cloudFoundrySecurityService"); - RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils - .getField(interceptorSecurityService, "restTemplate"); - assertThat(restTemplate.getRequestFactory()) - .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); - } - - @Test - public void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent() - throws Exception { - TestPropertyValues - .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") - .applyTo(this.context); - this.context.refresh(); - CloudFoundryEndpointHandlerMapping handlerMapping = this.context.getBean( - "cloudFoundryEndpointHandlerMapping", - CloudFoundryEndpointHandlerMapping.class); - Object securityInterceptor = ReflectionTestUtils.getField(handlerMapping, - "securityInterceptor"); - Object interceptorSecurityService = ReflectionTestUtils - .getField(securityInterceptor, "cloudFoundrySecurityService"); - assertThat(interceptorSecurityService).isNull(); - } - - @Test - public void cloudFoundryPathsIgnoredBySpringSecurity() throws Exception { - TestPropertyValues - .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") - .applyTo(this.context); - this.context.refresh(); - IgnoredRequestCustomizer customizer = (IgnoredRequestCustomizer) this.context - .getBean("cloudFoundryIgnoredRequestCustomizer"); - IgnoredRequestConfigurer configurer = mock(IgnoredRequestConfigurer.class); - customizer.customize(configurer); - ArgumentCaptor requestMatcher = ArgumentCaptor - .forClass(RequestMatcher.class); - verify(configurer).requestMatchers(requestMatcher.capture()); - RequestMatcher matcher = requestMatcher.getValue(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setServletPath("/cloudfoundryapplication/my-path"); - assertThat(matcher.matches(request)).isTrue(); - request.setServletPath("/some-other-path"); - assertThat(matcher.matches(request)).isFalse(); - } - - @Test - public void cloudFoundryPlatformInactive() throws Exception { - this.context.refresh(); - assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping")) - .isFalse(); - } - - @Test - public void cloudFoundryManagementEndpointsDisabled() throws Exception { - TestPropertyValues - .of("VCAP_APPLICATION=---", "management.cloudfoundry.enabled:false") - .applyTo(this.context); - this.context.refresh(); - assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping")) - .isFalse(); - } - - private CloudFoundryEndpointHandlerMapping getHandlerMapping() { - TestPropertyValues - .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", - "vcap.application.cf_api:http://my-cloud-controller.com") - .applyTo(this.context); - this.context.refresh(); - return this.context.getBean("cloudFoundryEndpointHandlerMapping", - CloudFoundryEndpointHandlerMapping.class); - } + // private AnnotationConfigWebApplicationContext context; + // + // @Before + // public void setup() { + // this.context = new AnnotationConfigWebApplicationContext(); + // this.context.setServletContext(new MockServletContext()); + // this.context.register(SecurityAutoConfiguration.class, + // WebMvcAutoConfiguration.class, + // ManagementWebSecurityAutoConfiguration.class, + // JacksonAutoConfiguration.class, + // HttpMessageConvertersAutoConfiguration.class, + // EndpointAutoConfiguration.class, EndpointServletWebAutoConfiguration.class, + // PropertyPlaceholderAutoConfiguration.class, + // RestTemplateAutoConfiguration.class, + // EndpointWebMvcManagementContextConfiguration.class, + // CloudFoundryActuatorAutoConfiguration.class); + // } + // + // @After + // public void close() { + // if (this.context != null) { + // this.context.close(); + // } + // } + // + // @Test + // public void cloudFoundryPlatformActive() throws Exception { + // CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + // assertThat(handlerMapping.getPrefix()).isEqualTo("/cloudfoundryapplication"); + // CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils + // .getField(handlerMapping, "corsConfiguration"); + // assertThat(corsConfiguration.getAllowedOrigins()).contains("*"); + // assertThat(corsConfiguration.getAllowedMethods()).containsAll( + // Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name())); + // assertThat(corsConfiguration.getAllowedHeaders()).containsAll( + // Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type")); + // } + // + // @Test + // public void cloudFoundryPlatformActiveSetsApplicationId() throws Exception { + // CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + // Object interceptor = ReflectionTestUtils.getField(handlerMapping, + // "securityInterceptor"); + // String applicationId = (String) ReflectionTestUtils.getField(interceptor, + // "applicationId"); + // assertThat(applicationId).isEqualTo("my-app-id"); + // } + // + // @Test + // public void cloudFoundryPlatformActiveSetsCloudControllerUrl() throws Exception { + // CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + // Object interceptor = ReflectionTestUtils.getField(handlerMapping, + // "securityInterceptor"); + // Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + // "cloudFoundrySecurityService"); + // String cloudControllerUrl = (String) ReflectionTestUtils + // .getField(interceptorSecurityService, "cloudControllerUrl"); + // assertThat(cloudControllerUrl).isEqualTo("http://my-cloud-controller.com"); + // } + // + // @Test + // public void skipSslValidation() throws Exception { + // TestPropertyValues.of("management.cloudfoundry.skipSslValidation:true") + // .applyTo(this.context); + // ConfigurationPropertySources.attach(this.context.getEnvironment()); + // this.context.refresh(); + // CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + // Object interceptor = ReflectionTestUtils.getField(handlerMapping, + // "securityInterceptor"); + // Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + // "cloudFoundrySecurityService"); + // RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils + // .getField(interceptorSecurityService, "restTemplate"); + // assertThat(restTemplate.getRequestFactory()) + // .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); + // } + // + // @Test + // public void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent() + // throws Exception { + // TestPropertyValues + // .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") + // .applyTo(this.context); + // this.context.refresh(); + // CloudFoundryEndpointHandlerMapping handlerMapping = this.context.getBean( + // "cloudFoundryEndpointHandlerMapping", + // CloudFoundryEndpointHandlerMapping.class); + // Object securityInterceptor = ReflectionTestUtils.getField(handlerMapping, + // "securityInterceptor"); + // Object interceptorSecurityService = ReflectionTestUtils + // .getField(securityInterceptor, "cloudFoundrySecurityService"); + // assertThat(interceptorSecurityService).isNull(); + // } + // + // @Test + // public void cloudFoundryPathsIgnoredBySpringSecurity() throws Exception { + // TestPropertyValues + // .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id") + // .applyTo(this.context); + // this.context.refresh(); + // IgnoredRequestCustomizer customizer = (IgnoredRequestCustomizer) this.context + // .getBean("cloudFoundryIgnoredRequestCustomizer"); + // IgnoredRequestConfigurer configurer = mock(IgnoredRequestConfigurer.class); + // customizer.customize(configurer); + // ArgumentCaptor requestMatcher = ArgumentCaptor + // .forClass(RequestMatcher.class); + // verify(configurer).requestMatchers(requestMatcher.capture()); + // RequestMatcher matcher = requestMatcher.getValue(); + // MockHttpServletRequest request = new MockHttpServletRequest(); + // request.setServletPath("/cloudfoundryapplication/my-path"); + // assertThat(matcher.matches(request)).isTrue(); + // request.setServletPath("/some-other-path"); + // assertThat(matcher.matches(request)).isFalse(); + // } + // + // @Test + // public void cloudFoundryPlatformInactive() throws Exception { + // this.context.refresh(); + // assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping")) + // .isFalse(); + // } + // + // @Test + // public void cloudFoundryManagementEndpointsDisabled() throws Exception { + // TestPropertyValues + // .of("VCAP_APPLICATION=---", "management.cloudfoundry.enabled:false") + // .applyTo(this.context); + // this.context.refresh(); + // assertThat(this.context.containsBean("cloudFoundryEndpointHandlerMapping")) + // .isFalse(); + // } + // + // private CloudFoundryEndpointHandlerMapping getHandlerMapping() { + // TestPropertyValues + // .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", + // "vcap.application.cf_api:http://my-cloud-controller.com") + // .applyTo(this.context); + // this.context.refresh(); + // return this.context.getBean("cloudFoundryEndpointHandlerMapping", + // CloudFoundryEndpointHandlerMapping.class); + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpointTests.java index 0d7b49255e..267f71df5f 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryDiscoveryMvcEndpointTests.java @@ -16,20 +16,6 @@ package org.springframework.boot.actuate.cloudfoundry; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; -import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; -import org.springframework.mock.web.MockHttpServletRequest; - -import static org.assertj.core.api.Assertions.assertThat; - /** * Tests for {@link CloudFoundryDiscoveryMvcEndpoint}. * @@ -37,85 +23,87 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class CloudFoundryDiscoveryMvcEndpointTests { - private CloudFoundryDiscoveryMvcEndpoint endpoint; + // TODO CloudFoundry support - private Set endpoints; - - @Before - public void setup() { - NamedMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - this.endpoints = new LinkedHashSet<>(); - this.endpoints.add(endpoint); - this.endpoint = new CloudFoundryDiscoveryMvcEndpoint(this.endpoints); - } - - @Test - public void cloudfoundryHalJsonEndpointHasEmptyPath() throws Exception { - assertThat(this.endpoint.getPath()).isEmpty(); - } - - @Test - public void linksResponseWhenRequestUriHasNoTrailingSlash() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", - "/cloudfoundryapplication"); - AccessLevel.FULL.put(request); - Map links = this.endpoint - .links(request).get("_links"); - assertThat(links.get("self").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication"); - assertThat(links.get("a").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication/a"); - } - - @Test - public void linksResponseWhenRequestUriHasTrailingSlash() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", - "/cloudfoundryapplication/"); - AccessLevel.FULL.put(request); - Map links = this.endpoint - .links(request).get("_links"); - assertThat(links.get("self").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication"); - assertThat(links.get("a").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication/a"); - } - - @Test - public void linksResponseWhenRequestHasAccessLevelRestricted() throws Exception { - NamedMvcEndpoint testHealthMvcEndpoint = new TestMvcEndpoint( - new TestEndpoint("health")); - this.endpoints.add(testHealthMvcEndpoint); - MockHttpServletRequest request = new MockHttpServletRequest("GET", - "/cloudfoundryapplication/"); - AccessLevel.RESTRICTED.put(request); - Map links = this.endpoint - .links(request).get("_links"); - assertThat(links.get("self").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication"); - assertThat(links.get("health").getHref()) - .isEqualTo("http://localhost/cloudfoundryapplication/health"); - assertThat(links.get("a")).isNull(); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } + // private CloudFoundryDiscoveryMvcEndpoint endpoint; + // + // private Set endpoints; + // + // @Before + // public void setup() { + // NamedMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); + // this.endpoints = new LinkedHashSet<>(); + // this.endpoints.add(endpoint); + // this.endpoint = new CloudFoundryDiscoveryMvcEndpoint(this.endpoints); + // } + // + // @Test + // public void cloudfoundryHalJsonEndpointHasEmptyPath() throws Exception { + // assertThat(this.endpoint.getPath()).isEmpty(); + // } + // + // @Test + // public void linksResponseWhenRequestUriHasNoTrailingSlash() throws Exception { + // MockHttpServletRequest request = new MockHttpServletRequest("GET", + // "/cloudfoundryapplication"); + // AccessLevel.FULL.put(request); + // Map links = this.endpoint + // .links(request).get("_links"); + // assertThat(links.get("self").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication"); + // assertThat(links.get("a").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication/a"); + // } + // + // @Test + // public void linksResponseWhenRequestUriHasTrailingSlash() throws Exception { + // MockHttpServletRequest request = new MockHttpServletRequest("GET", + // "/cloudfoundryapplication/"); + // AccessLevel.FULL.put(request); + // Map links = this.endpoint + // .links(request).get("_links"); + // assertThat(links.get("self").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication"); + // assertThat(links.get("a").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication/a"); + // } + // + // @Test + // public void linksResponseWhenRequestHasAccessLevelRestricted() throws Exception { + // NamedMvcEndpoint testHealthMvcEndpoint = new TestMvcEndpoint( + // new TestEndpoint("health")); + // this.endpoints.add(testHealthMvcEndpoint); + // MockHttpServletRequest request = new MockHttpServletRequest("GET", + // "/cloudfoundryapplication/"); + // AccessLevel.RESTRICTED.put(request); + // Map links = this.endpoint + // .links(request).get("_links"); + // assertThat(links.get("self").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication"); + // assertThat(links.get("health").getHref()) + // .isEqualTo("http://localhost/cloudfoundryapplication/health"); + // assertThat(links.get("a")).isNull(); + // } + // + // private static class TestEndpoint extends AbstractEndpoint { + // + // TestEndpoint(String id) { + // super(id); + // } + // + // @Override + // public Object invoke() { + // return null; + // } + // + // } + // + // private static class TestMvcEndpoint extends EndpointMvcAdapter { + // + // TestMvcEndpoint(TestEndpoint delegate) { + // super(delegate); + // } + // + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMappingTests.java index 19f1f93891..05afb33341 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMappingTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryEndpointHandlerMappingTests.java @@ -16,102 +16,110 @@ package org.springframework.boot.actuate.cloudfoundry; -import java.util.Collections; - -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMappingTests; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; -import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.HandlerExecutionChain; - -import static org.assertj.core.api.Assertions.assertThat; - /** * Tests for {@link CloudFoundryEndpointHandlerMapping}. * * @author Madhura Bhave */ -public class CloudFoundryEndpointHandlerMappingTests - extends AbstractEndpointHandlerMappingTests { +public class CloudFoundryEndpointHandlerMappingTests { - @Test - public void getHandlerExecutionChainWhenEndpointHasPathShouldMapAgainstName() - throws Exception { - TestMvcEndpoint testMvcEndpoint = new TestMvcEndpoint(new TestEndpoint("a")); - testMvcEndpoint.setPath("something-else"); - CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping( - Collections.singleton(testMvcEndpoint), null, null); - assertThat(handlerMapping.getPath(testMvcEndpoint)).isEqualTo("/a"); - } + // TODO CloudFoundry support - @Test - public void registersCloudFoundryDiscoveryEndpoint() throws Exception { - StaticApplicationContext context = new StaticApplicationContext(); - CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping( - Collections.emptySet(), null, null); - handlerMapping.setPrefix("/test"); - handlerMapping.setApplicationContext(context); - handlerMapping.afterPropertiesSet(); - HandlerExecutionChain handler = handlerMapping - .getHandler(new MockHttpServletRequest("GET", "/test")); - HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); - assertThat(handlerMethod.getBean()) - .isInstanceOf(CloudFoundryDiscoveryMvcEndpoint.class); - } - - @Test - public void registersCloudFoundryHealthEndpoint() throws Exception { - StaticApplicationContext context = new StaticApplicationContext(); - HealthEndpoint delegate = new HealthEndpoint(new OrderedHealthAggregator(), - Collections.emptyMap()); - CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping( - Collections.singleton(new TestHealthMvcEndpoint(delegate)), null, null); - handlerMapping.setPrefix("/test"); - handlerMapping.setApplicationContext(context); - handlerMapping.afterPropertiesSet(); - HandlerExecutionChain handler = handlerMapping - .getHandler(new MockHttpServletRequest("GET", "/test/health")); - HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); - Object handlerMethodBean = handlerMethod.getBean(); - assertThat(handlerMethodBean).isInstanceOf(CloudFoundryHealthMvcEndpoint.class); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - - private static class TestHealthMvcEndpoint extends HealthMvcEndpoint { - - TestHealthMvcEndpoint(HealthEndpoint delegate) { - super(delegate); - } - - } + // @Test + // public void getHandlerExecutionChainWhenEndpointHasPathShouldMapAgainstName() + // throws Exception { + // TestMvcEndpoint testMvcEndpoint = new TestMvcEndpoint(new TestEndpoint("a")); + // testMvcEndpoint.setPath("something-else"); + // CloudFoundryEndpointHandlerMapping handlerMapping = new + // CloudFoundryEndpointHandlerMapping( + // Collections.singleton(testMvcEndpoint), null, null); + // assertThat(handlerMapping.getPath(testMvcEndpoint)).isEqualTo("/a"); + // } + // + // @Test + // public void doesNotRegisterHalJsonMvcEndpoint() throws Exception { + // CloudFoundryEndpointHandlerMapping handlerMapping = new + // CloudFoundryEndpointHandlerMapping( + // Collections.singleton(new TestHalJsonMvcEndpoint()), null, null); + // assertThat(handlerMapping.getEndpoints()).hasSize(0); + // } + // + // @Test + // public void registersCloudFoundryDiscoveryEndpoint() throws Exception { + // StaticApplicationContext context = new StaticApplicationContext(); + // CloudFoundryEndpointHandlerMapping handlerMapping = new + // CloudFoundryEndpointHandlerMapping( + // Collections.emptySet(), null, null); + // handlerMapping.setPrefix("/test"); + // handlerMapping.setApplicationContext(context); + // handlerMapping.afterPropertiesSet(); + // HandlerExecutionChain handler = handlerMapping + // .getHandler(new MockHttpServletRequest("GET", "/test")); + // HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); + // assertThat(handlerMethod.getBean()) + // .isInstanceOf(CloudFoundryDiscoveryMvcEndpoint.class); + // } + // + // @Test + // public void registersCloudFoundryHealthEndpoint() throws Exception { + // StaticApplicationContext context = new StaticApplicationContext(); + // HealthEndpoint delegate = new HealthEndpoint(new OrderedHealthAggregator(), + // Collections.emptyMap()); + // CloudFoundryEndpointHandlerMapping handlerMapping = new + // CloudFoundryEndpointHandlerMapping( + // Collections.singleton(new TestHealthMvcEndpoint(delegate)), null, null); + // handlerMapping.setPrefix("/test"); + // handlerMapping.setApplicationContext(context); + // handlerMapping.afterPropertiesSet(); + // HandlerExecutionChain handler = handlerMapping + // .getHandler(new MockHttpServletRequest("GET", "/test/health")); + // HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); + // Object handlerMethodBean = handlerMethod.getBean(); + // assertThat(handlerMethodBean).isInstanceOf(CloudFoundryHealthMvcEndpoint.class); + // } + // + // private static class TestEndpoint extends AbstractEndpoint { + // + // TestEndpoint(String id) { + // super(id); + // } + // + // @Override + // public Object invoke() { + // return null; + // } + // + // } + // + // private static class TestMvcEndpoint extends EndpointMvcAdapter { + // + // TestMvcEndpoint(TestEndpoint delegate) { + // super(delegate); + // } + // + // } + // + // private static class TestHalJsonMvcEndpoint extends HalJsonMvcEndpoint { + // + // TestHalJsonMvcEndpoint() { + // super(new ManagementServletContext() { + // + // @Override + // public String getContextPath() { + // return ""; + // } + // + // }); + // } + // + // } + // + // private static class TestHealthMvcEndpoint extends HealthWebEndpointExtension { + // + // TestHealthMvcEndpoint(HealthEndpoint delegate) { + // super(delegate); + // } + // + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpointTests.java index b8203503e1..a10f5705d7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryHealthMvcEndpointTests.java @@ -16,16 +16,6 @@ package org.springframework.boot.actuate.cloudfoundry; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Status; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - /** * Tests for {@link CloudFoundryHealthMvcEndpoint}. * @@ -33,18 +23,21 @@ import static org.mockito.Mockito.mock; */ public class CloudFoundryHealthMvcEndpointTests { - @Test - public void cloudFoundryHealthEndpointShouldAlwaysReturnAllHealthDetails() - throws Exception { - HealthEndpoint endpoint = mock(HealthEndpoint.class); - given(endpoint.isEnabled()).willReturn(true); - CloudFoundryHealthMvcEndpoint mvc = new CloudFoundryHealthMvcEndpoint(endpoint); - given(endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = mvc.invoke(null, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } + // TODO CloudFoundry-specific health endpoint? + + // @Test + // public void cloudFoundryHealthEndpointShouldAlwaysReturnAllHealthDetails() + // throws Exception { + // HealthEndpoint endpoint = mock(HealthEndpoint.class); + // given(endpoint.isEnabled()).willReturn(true); + // CloudFoundryHealthMvcEndpoint mvc = new CloudFoundryHealthMvcEndpoint(endpoint); + // given(endpoint.invoke()) + // .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); + // given(endpoint.isSensitive()).willReturn(false); + // Object result = mvc.invoke(null, null); + // assertThat(result instanceof Health).isTrue(); + // assertThat(((Health) result).getStatus() == Status.UP).isTrue(); + // assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java index 7a338d210d..6806991db7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptorTests.java @@ -16,27 +16,6 @@ package org.springframework.boot.actuate.cloudfoundry; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.BDDMockito; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.Base64Utils; -import org.springframework.web.method.HandlerMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; - /** * Tests for {@link CloudFoundrySecurityInterceptor}. * @@ -44,168 +23,170 @@ import static org.mockito.Mockito.verify; */ public class CloudFoundrySecurityInterceptorTests { - @Mock - private TokenValidator tokenValidator; + // TODO CloudFoundry-specific security - @Mock - private CloudFoundrySecurityService securityService; - - private CloudFoundrySecurityInterceptor interceptor; - - private TestMvcEndpoint endpoint; - - private HandlerMethod handlerMethod; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, - this.securityService, "my-app-id"); - this.endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); - this.request = new MockHttpServletRequest(); - this.response = new MockHttpServletResponse(); - } - - @Test - public void preHandleWhenRequestIsPreFlightShouldReturnTrue() throws Exception { - this.request.setMethod("OPTIONS"); - this.request.addHeader(HttpHeaders.ORIGIN, "http://example.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isTrue(); - } - - @Test - public void preHandleWhenTokenIsMissingShouldReturnFalse() throws Exception { - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value()); - assertThat(this.response.getContentAsString()).contains("security_error"); - assertThat(this.response.getContentType()) - .isEqualTo(MediaType.APPLICATION_JSON.toString()); - } - - @Test - public void preHandleWhenTokenIsNotBearerShouldReturnFalse() throws Exception { - this.request.addHeader("Authorization", mockAccessToken()); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value()); - } - - @Test - public void preHandleWhenApplicationIdIsNullShouldReturnFalse() throws Exception { - this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, - this.securityService, null); - this.request.addHeader("Authorization", "bearer " + mockAccessToken()); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value()); - } - - @Test - public void preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnFalse() - throws Exception { - this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, null, - "my-app-id"); - this.request.addHeader("Authorization", "bearer " + mockAccessToken()); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value()); - } - - @Test - public void preHandleWhenAccessIsNotAllowedShouldReturnFalse() throws Exception { - this.endpoint = new TestMvcEndpoint(new TestEndpoint("env")); - this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); - String accessToken = mockAccessToken(); - this.request.addHeader("Authorization", "bearer " + accessToken); - BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) - .willReturn(AccessLevel.RESTRICTED); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - assertThat(preHandle).isFalse(); - assertThat(this.response.getStatus()) - .isEqualTo(Reason.ACCESS_DENIED.getStatus().value()); - } - - @Test - public void preHandleSuccessfulWithFullAccess() throws Exception { - String accessToken = mockAccessToken(); - this.request.addHeader("Authorization", "Bearer " + accessToken); - BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) - .willReturn(AccessLevel.FULL); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); - verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); - Token token = tokenArgumentCaptor.getValue(); - assertThat(token.toString()).isEqualTo(accessToken); - assertThat(preHandle).isTrue(); - assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(this.request.getAttribute("cloudFoundryAccessLevel")) - .isEqualTo(AccessLevel.FULL); - } - - @Test - public void preHandleSuccessfulWithRestrictedAccess() throws Exception { - this.endpoint = new TestMvcEndpoint(new TestEndpoint("info")); - this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); - String accessToken = mockAccessToken(); - this.request.addHeader("Authorization", "Bearer " + accessToken); - BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) - .willReturn(AccessLevel.RESTRICTED); - boolean preHandle = this.interceptor.preHandle(this.request, this.response, - this.handlerMethod); - ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); - verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); - Token token = tokenArgumentCaptor.getValue(); - assertThat(token.toString()).isEqualTo(accessToken); - assertThat(preHandle).isTrue(); - assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(this.request.getAttribute("cloudFoundryAccessLevel")) - .isEqualTo(AccessLevel.RESTRICTED); - } - - private String mockAccessToken() { - return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu" - + "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ." - + Base64Utils.encodeToString("signature".getBytes()); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } + // @Mock + // private TokenValidator tokenValidator; + // + // @Mock + // private CloudFoundrySecurityService securityService; + // + // private CloudFoundrySecurityInterceptor interceptor; + // + // private TestMvcEndpoint endpoint; + // + // private HandlerMethod handlerMethod; + // + // private MockHttpServletRequest request; + // + // private MockHttpServletResponse response; + // + // @Before + // public void setup() throws Exception { + // MockitoAnnotations.initMocks(this); + // this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, + // this.securityService, "my-app-id"); + // this.endpoint = new TestMvcEndpoint(new TestEndpoint("a")); + // this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); + // this.request = new MockHttpServletRequest(); + // this.response = new MockHttpServletResponse(); + // } + // + // @Test + // public void preHandleWhenRequestIsPreFlightShouldReturnTrue() throws Exception { + // this.request.setMethod("OPTIONS"); + // this.request.addHeader(HttpHeaders.ORIGIN, "http://example.com"); + // this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isTrue(); + // } + // + // @Test + // public void preHandleWhenTokenIsMissingShouldReturnFalse() throws Exception { + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value()); + // assertThat(this.response.getContentAsString()).contains("security_error"); + // assertThat(this.response.getContentType()) + // .isEqualTo(MediaType.APPLICATION_JSON.toString()); + // } + // + // @Test + // public void preHandleWhenTokenIsNotBearerShouldReturnFalse() throws Exception { + // this.request.addHeader("Authorization", mockAccessToken()); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus().value()); + // } + // + // @Test + // public void preHandleWhenApplicationIdIsNullShouldReturnFalse() throws Exception { + // this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, + // this.securityService, null); + // this.request.addHeader("Authorization", "bearer " + mockAccessToken()); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value()); + // } + // + // @Test + // public void preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnFalse() + // throws Exception { + // this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, null, + // "my-app-id"); + // this.request.addHeader("Authorization", "bearer " + mockAccessToken()); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus().value()); + // } + // + // @Test + // public void preHandleWhenAccessIsNotAllowedShouldReturnFalse() throws Exception { + // this.endpoint = new TestMvcEndpoint(new TestEndpoint("env")); + // this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); + // String accessToken = mockAccessToken(); + // this.request.addHeader("Authorization", "bearer " + accessToken); + // BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) + // .willReturn(AccessLevel.RESTRICTED); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // assertThat(preHandle).isFalse(); + // assertThat(this.response.getStatus()) + // .isEqualTo(Reason.ACCESS_DENIED.getStatus().value()); + // } + // + // @Test + // public void preHandleSuccessfulWithFullAccess() throws Exception { + // String accessToken = mockAccessToken(); + // this.request.addHeader("Authorization", "Bearer " + accessToken); + // BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) + // .willReturn(AccessLevel.FULL); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); + // verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); + // Token token = tokenArgumentCaptor.getValue(); + // assertThat(token.toString()).isEqualTo(accessToken); + // assertThat(preHandle).isTrue(); + // assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); + // assertThat(this.request.getAttribute("cloudFoundryAccessLevel")) + // .isEqualTo(AccessLevel.FULL); + // } + // + // @Test + // public void preHandleSuccessfulWithRestrictedAccess() throws Exception { + // this.endpoint = new TestMvcEndpoint(new TestEndpoint("info")); + // this.handlerMethod = new HandlerMethod(this.endpoint, "invoke"); + // String accessToken = mockAccessToken(); + // this.request.addHeader("Authorization", "Bearer " + accessToken); + // BDDMockito.given(this.securityService.getAccessLevel(accessToken, "my-app-id")) + // .willReturn(AccessLevel.RESTRICTED); + // boolean preHandle = this.interceptor.preHandle(this.request, this.response, + // this.handlerMethod); + // ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); + // verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); + // Token token = tokenArgumentCaptor.getValue(); + // assertThat(token.toString()).isEqualTo(accessToken); + // assertThat(preHandle).isTrue(); + // assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); + // assertThat(this.request.getAttribute("cloudFoundryAccessLevel")) + // .isEqualTo(AccessLevel.RESTRICTED); + // } + // + // private String mockAccessToken() { + // return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu" + // + "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ." + // + Base64Utils.encodeToString("signature".getBytes()); + // } + // + // private static class TestEndpoint extends AbstractEndpoint { + // + // TestEndpoint(String id) { + // super(id); + // } + // + // @Override + // public Object invoke() { + // return null; + // } + // + // } + // + // private static class TestMvcEndpoint extends EndpointMvcAdapter { + // + // TestMvcEndpoint(TestEndpoint delegate) { + // super(delegate); + // } + // + // } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AbstractEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AbstractEndpointTests.java deleted file mode 100644 index 00d285e140..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AbstractEndpointTests.java +++ /dev/null @@ -1,156 +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.Collections; -import java.util.HashMap; -import java.util.Map; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.PropertySource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Abstract base class for endpoint tests. - * - * @param the endpoint type - * @author Phillip Webb - */ -public abstract class AbstractEndpointTests> { - - protected AnnotationConfigApplicationContext context; - - protected final Class configClass; - - private final Class type; - - private final String id; - - private final String property; - - public AbstractEndpointTests(Class configClass, Class type, String id, - String property) { - this.configClass = configClass; - this.type = type; - this.id = id; - this.property = property; - } - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(JacksonAutoConfiguration.class, this.configClass); - this.context.refresh(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void getId() throws Exception { - assertThat(getEndpointBean().getId()).isEqualTo(this.id); - } - - @Test - public void idOverride() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of(this.property + ".id:myid").applyTo(this.context); - this.context.register(this.configClass); - this.context.refresh(); - assertThat(getEndpointBean().getId()).isEqualTo("myid"); - } - - @Test - public void isEnabledByDefault() throws Exception { - assertThat(getEndpointBean().isEnabled()).isTrue(); - } - - @Test - public void isEnabledFallbackToEnvironment() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - PropertySource propertySource = new MapPropertySource("test", Collections - .singletonMap(this.property + ".enabled", false)); - this.context.getEnvironment().getPropertySources().addFirst(propertySource); - this.context.register(this.configClass); - this.context.refresh(); - assertThat(getEndpointBean().isEnabled()).isFalse(); - } - - @Test - @SuppressWarnings("rawtypes") - public void isExplicitlyEnabled() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - PropertySource propertySource = new MapPropertySource("test", Collections - .singletonMap(this.property + ".enabled", false)); - this.context.getEnvironment().getPropertySources().addFirst(propertySource); - this.context.register(this.configClass); - this.context.refresh(); - ((AbstractEndpoint) getEndpointBean()).setEnabled(true); - assertThat(getEndpointBean().isEnabled()).isTrue(); - } - - @Test - public void isAllEndpointsDisabled() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - PropertySource propertySource = new MapPropertySource("test", - Collections.singletonMap("endpoints.enabled", false)); - this.context.getEnvironment().getPropertySources().addFirst(propertySource); - this.context.register(this.configClass); - this.context.refresh(); - assertThat(getEndpointBean().isEnabled()).isFalse(); - } - - @Test - public void isOptIn() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - Map source = new HashMap<>(); - source.put("endpoints.enabled", false); - source.put(this.property + ".enabled", true); - PropertySource propertySource = new MapPropertySource("test", source); - this.context.getEnvironment().getPropertySources().addFirst(propertySource); - this.context.register(this.configClass); - this.context.refresh(); - assertThat(getEndpointBean().isEnabled()).isTrue(); - } - - @Test - public void serialize() throws Exception { - Object result = getEndpointBean().invoke(); - if (result != null) { - this.context.getBean(ObjectMapper.class).writeValue(System.out, result); - } - } - - @SuppressWarnings("unchecked") - protected T getEndpointBean() { - return (T) this.context.getBean(this.type); - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpointTests.java new file mode 100644 index 0000000000..37e9731bd1 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AuditEventsEndpointTests.java @@ -0,0 +1,74 @@ +/* + * 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.Collections; +import java.util.Date; +import java.util.List; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AuditEventsEndpoint}. + * + * @author Andy Wilkinson + */ +public class AuditEventsEndpointTests { + + private final AuditEventRepository repository = mock(AuditEventRepository.class); + + private final AuditEventsEndpoint endpoint = new AuditEventsEndpoint(this.repository); + + private final AuditEvent event = new AuditEvent("principal", "type", + Collections.singletonMap("a", "alpha")); + + @Test + public void eventsWithType() { + given(this.repository.find(null, null, "type")) + .willReturn(Collections.singletonList(this.event)); + List result = this.endpoint.eventsWithPrincipalDateAfterAndType(null, + null, "type"); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + + @Test + public void eventsWithDateAfter() { + Date date = new Date(); + given(this.repository.find(null, date, null)) + .willReturn(Collections.singletonList(this.event)); + List result = this.endpoint.eventsWithPrincipalDateAfterAndType(null, + date, null); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + + @Test + public void eventsWithPrincipal() { + given(this.repository.find("Joan", null, null)) + .willReturn(Collections.singletonList(this.event)); + List result = this.endpoint + .eventsWithPrincipalDateAfterAndType("Joan", null, null); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java index da1f331d2a..c9a97ba586 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java @@ -27,8 +27,8 @@ import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.Configuration; @@ -43,24 +43,19 @@ import static org.mockito.Mockito.mock; * @author Phillip Webb * @author Andy Wilkinson */ -public class AutoConfigurationReportEndpointTests - extends AbstractEndpointTests { - - public AutoConfigurationReportEndpointTests() { - super(Config.class, AutoConfigurationReportEndpoint.class, "autoconfig", - "endpoints.autoconfig"); - } +public class AutoConfigurationReportEndpointTests { @Test public void invoke() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(this.configClass); - this.context.refresh(); - Report report = getEndpointBean().invoke(); - assertThat(report.getPositiveMatches()).isEmpty(); - assertThat(report.getNegativeMatches()).containsKey("a"); - assertThat(report.getUnconditionalClasses()).contains("b"); - assertThat(report.getExclusions()).contains("com.foo.Bar"); + new ApplicationContextRunner().withUserConfiguration(Config.class) + .run((context) -> { + Report report = context.getBean(AutoConfigurationReportEndpoint.class) + .getEvaluationReport(); + assertThat(report.getPositiveMatches()).isEmpty(); + assertThat(report.getNegativeMatches()).containsKey("a"); + assertThat(report.getUnconditionalClasses()).contains("b"); + assertThat(report.getExclusions()).contains("com.foo.Bar"); + }); } @Configuration @@ -84,8 +79,9 @@ public class AutoConfigurationReportEndpointTests } @Bean - public AutoConfigurationReportEndpoint endpoint() { - return new AutoConfigurationReportEndpoint(); + public AutoConfigurationReportEndpoint endpoint( + ConditionEvaluationReport report) { + return new AutoConfigurationReportEndpoint(report); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointContextHierarchyTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointContextHierarchyTests.java deleted file mode 100644 index dea3ce94b6..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointContextHierarchyTests.java +++ /dev/null @@ -1,137 +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.List; -import java.util.Map; - -import org.assertj.core.api.Condition; -import org.junit.After; -import org.junit.Test; - -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link BeansEndpoint} with an application context hierarchy. - * - * @author Andy Wilkinson - */ -public class BeansEndpointContextHierarchyTests { - - private AnnotationConfigApplicationContext parent; - - private AnnotationConfigApplicationContext child; - - @After - public void closeContexts() { - if (this.child != null) { - this.child.close(); - } - if (this.parent != null) { - this.parent.close(); - } - } - - @Test - public void beansInParentContextAreFound() { - load(BeanConfiguration.class, EndpointConfiguration.class); - BeansEndpoint endpoint = this.child.getBean(BeansEndpoint.class); - List contexts = endpoint.invoke(); - assertThat(contexts).hasSize(2); - assertThat(contexts.get(1)).has(beanNamed("bean")); - assertThat(contexts.get(0)).has(beanNamed("endpoint")); - } - - @Test - public void beansInChildContextAreNotFound() { - load(EndpointConfiguration.class, BeanConfiguration.class); - BeansEndpoint endpoint = this.child.getBean(BeansEndpoint.class); - List contexts = endpoint.invoke(); - assertThat(contexts).hasSize(1); - assertThat(contexts.get(0)).has(beanNamed("endpoint")); - assertThat(contexts.get(0)).doesNotHave(beanNamed("bean")); - } - - private void load(Class parent, Class child) { - this.parent = new AnnotationConfigApplicationContext(parent); - this.child = new AnnotationConfigApplicationContext(); - this.child.setParent(this.parent); - this.child.register(child); - this.child.refresh(); - } - - private ContextHasBeanCondition beanNamed(String beanName) { - return new ContextHasBeanCondition(beanName); - } - - private static final class ContextHasBeanCondition extends Condition { - - private final String beanName; - - private ContextHasBeanCondition(String beanName) { - this.beanName = beanName; - } - - @Override - @SuppressWarnings("unchecked") - public boolean matches(Object context) { - if (!(context instanceof Map)) { - return false; - } - List beans = (List) ((Map) context) - .get("beans"); - if (beans == null) { - return false; - } - for (Object bean : beans) { - if (!(bean instanceof Map)) { - return false; - } - if (this.beanName.equals(((Map) bean).get("bean"))) { - return true; - } - } - return false; - } - - } - - @Configuration - static class EndpointConfiguration { - - @Bean - public BeansEndpoint endpoint() { - return new BeansEndpoint(); - } - - } - - @Configuration - static class BeanConfiguration { - - @Bean - public String bean() { - return "bean"; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointTests.java index 57559ea0af..2d9145432b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/BeansEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 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. @@ -19,9 +19,10 @@ package org.springframework.boot.actuate.endpoint; import java.util.List; import java.util.Map; +import org.assertj.core.api.Condition; import org.junit.Test; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,23 +32,93 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link BeansEndpoint}. * * @author Phillip Webb + * @author Andy Wilkinson */ -public class BeansEndpointTests extends AbstractEndpointTests { +public class BeansEndpointTests { - public BeansEndpointTests() { - super(Config.class, BeansEndpoint.class, "beans", "endpoints.beans"); + @Test + public void beansAreFound() throws Exception { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(EndpointConfiguration.class); + contextRunner.run((context) -> { + List result = context.getBean(BeansEndpoint.class).beans(); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isInstanceOf(Map.class); + }); } @Test - public void invoke() throws Exception { - List result = getEndpointBean().invoke(); - assertThat(result).hasSize(1); - assertThat(result.get(0)).isInstanceOf(Map.class); + public void beansInParentContextAreFound() { + ApplicationContextRunner parentRunner = new ApplicationContextRunner() + .withUserConfiguration(BeanConfiguration.class); + parentRunner.run((parent) -> { + new ApplicationContextRunner() + .withUserConfiguration(EndpointConfiguration.class).withParent(parent) + .run(child -> { + BeansEndpoint endpoint = child.getBean(BeansEndpoint.class); + List contexts = endpoint.beans(); + assertThat(contexts).hasSize(2); + assertThat(contexts.get(1)).has(beanNamed("bean")); + assertThat(contexts.get(0)).has(beanNamed("endpoint")); + }); + }); + } + + @Test + public void beansInChildContextAreNotFound() { + ApplicationContextRunner parentRunner = new ApplicationContextRunner() + .withUserConfiguration(EndpointConfiguration.class); + parentRunner.run((parent) -> { + new ApplicationContextRunner().withUserConfiguration(BeanConfiguration.class) + .withParent(parent).run(child -> { + BeansEndpoint endpoint = child.getBean(BeansEndpoint.class); + List contexts = endpoint.beans(); + assertThat(contexts).hasSize(1); + assertThat(contexts.get(0)).has(beanNamed("endpoint")); + assertThat(contexts.get(0)).doesNotHave(beanNamed("bean")); + }); + }); + } + + private ContextHasBeanCondition beanNamed(String beanName) { + return new ContextHasBeanCondition(beanName); + } + + private static final class ContextHasBeanCondition extends Condition { + + private final String beanName; + + private ContextHasBeanCondition(String beanName) { + super("Bean named '" + beanName + "'"); + this.beanName = beanName; + } + + @Override + @SuppressWarnings("unchecked") + public boolean matches(Object context) { + if (!(context instanceof Map)) { + return false; + } + List beans = (List) ((Map) context) + .get("beans"); + if (beans == null) { + return false; + } + for (Object bean : beans) { + if (!(bean instanceof Map)) { + return false; + } + if (this.beanName.equals(((Map) bean).get("bean"))) { + return true; + } + } + return false; + } + } @Configuration - @EnableConfigurationProperties - public static class Config { + public static class EndpointConfiguration { @Bean public BeansEndpoint endpoint() { @@ -56,4 +127,14 @@ public class BeansEndpointTests extends AbstractEndpointTests { } + @Configuration + static class BeanConfiguration { + + @Bean + public String bean() { + return "bean"; + } + + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java index 5a8ca1f54b..05d28f7664 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java @@ -18,14 +18,11 @@ package org.springframework.boot.actuate.endpoint; import java.util.Map; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,53 +32,44 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ConfigurationPropertiesReportEndpoint} when used with bean methods. * * @author Dave Syer + * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointMethodAnnotationsTests { - private AnnotationConfigApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } + @Test + @SuppressWarnings("unchecked") + public void testNaming() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(Config.class) + .withPropertyValues("other.name:foo", "first.name:bar"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("other"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("other"); + assertThat(nestedProperties.get("properties")).isNotNull(); + }); } @Test @SuppressWarnings("unchecked") - public void testNaming() throws Exception { - this.context.register(Config.class); - TestPropertyValues.of("other.name:foo", "first.name:bar").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("other"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("other"); - assertThat(nestedProperties.get("properties")).isNotNull(); - } - - @Test - @SuppressWarnings("unchecked") - public void testOverride() throws Exception { - this.context.register(Other.class); - TestPropertyValues.of("other.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("bar"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("other"); - assertThat(nestedProperties.get("properties")).isNotNull(); + public void prefixFromBeanMethodConfigurationPropertiesCanOverridePrefixOnClass() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(OverriddenPrefix.class) + .withPropertyValues("other.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("bar"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("other"); + assertThat(nestedProperties.get("properties")).isNotNull(); + }); } @Configuration @@ -109,7 +97,7 @@ public class ConfigurationPropertiesReportEndpointMethodAnnotationsTests { @Configuration @EnableConfigurationProperties - public static class Other { + public static class OverriddenPrefix { @Bean public ConfigurationPropertiesReportEndpoint endpoint() { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointParentTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointParentTests.java index 75157a987a..0986894a96 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointParentTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointParentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 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. @@ -18,13 +18,11 @@ package org.springframework.boot.actuate.endpoint; import java.util.Map; -import org.junit.After; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,53 +33,47 @@ import static org.assertj.core.api.Assertions.assertThat; * context. * * @author Dave Syer + * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointParentTests { - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - if (this.context.getParent() != null) { - ((ConfigurableApplicationContext) this.context.getParent()).close(); - } - } + @Test + @SuppressWarnings("unchecked") + public void configurationPropertiesClass() throws Exception { + new ApplicationContextRunner().withUserConfiguration(Parent.class) + .run((parent) -> { + new ApplicationContextRunner() + .withUserConfiguration(ClassConfigurationProperties.class) + .withParent(parent).run(child -> { + ConfigurationPropertiesReportEndpoint endpoint = child + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map result = endpoint.configurationProperties(); + assertThat(result.keySet()).containsExactlyInAnyOrder("parent", + "endpoint", "someProperties"); + assertThat(((Map) result.get("parent")).keySet()) + .containsExactly("testProperties"); + }); + }); } @Test - public void testInvoke() throws Exception { - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(Parent.class); - parent.refresh(); - this.context = new AnnotationConfigApplicationContext(); - this.context.setParent(parent); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint endpoint = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map result = endpoint.invoke(); - assertThat(result).containsKey("parent"); - assertThat(result).hasSize(3); // the endpoint, the test props and the parent - // System.err.println(result); - } - - @Test - public void testInvokeWithFactory() throws Exception { - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(Parent.class); - parent.refresh(); - this.context = new AnnotationConfigApplicationContext(); - this.context.setParent(parent); - this.context.register(Factory.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint endpoint = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map result = endpoint.invoke(); - assertThat(result.containsKey("parent")).isTrue(); - assertThat(result).hasSize(3); // the endpoint, the test props and the parent - // System.err.println(result); + @SuppressWarnings("unchecked") + public void configurationPropertiesBeanMethod() throws Exception { + new ApplicationContextRunner().withUserConfiguration(Parent.class) + .run((parent) -> { + new ApplicationContextRunner() + .withUserConfiguration( + BeanMethodConfigurationProperties.class) + .withParent(parent).run(child -> { + ConfigurationPropertiesReportEndpoint endpoint = child + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map result = endpoint.configurationProperties(); + assertThat(result.keySet()).containsExactlyInAnyOrder("parent", + "endpoint", "otherProperties"); + assertThat(((Map) result.get("parent")).keySet()) + .containsExactly("testProperties"); + }); + }); } @Configuration @@ -97,7 +89,7 @@ public class ConfigurationPropertiesReportEndpointParentTests { @Configuration @EnableConfigurationProperties - public static class Config { + public static class ClassConfigurationProperties { @Bean public ConfigurationPropertiesReportEndpoint endpoint() { @@ -113,7 +105,7 @@ public class ConfigurationPropertiesReportEndpointParentTests { @Configuration @EnableConfigurationProperties - public static class Factory { + public static class BeanMethodConfigurationProperties { @Bean public ConfigurationPropertiesReportEndpoint endpoint() { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointProxyTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointProxyTests.java index 89286011e3..267f87e314 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointProxyTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 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. @@ -20,13 +20,11 @@ import java.util.Map; import javax.sql.DataSource; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; @@ -45,30 +43,20 @@ import static org.assertj.core.api.Assertions.assertThat; * class. * * @author Phillip Webb + * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointProxyTests { - private AnnotationConfigApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - @Test public void testWithProxyClass() throws Exception { - this.context.register(Config.class, SqlExecutor.class); - this.context.refresh(); - Map report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class).invoke(); - assertThat(report.toString()).contains("prefix=executor.sql"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(Config.class, SqlExecutor.class); + contextRunner.run((context) -> { + Map report = context + .getBean(ConfigurationPropertiesReportEndpoint.class) + .configurationProperties(); + assertThat(report.toString()).contains("prefix=executor.sql"); + }); } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java index 37f8da43b5..df3ab8082b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java @@ -22,14 +22,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -42,185 +39,190 @@ import static org.assertj.core.api.Assertions.entry; * * @author Dave Syer * @author Stephane Nicoll + * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointSerializationTests { - private AnnotationConfigApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - @Test @SuppressWarnings("unchecked") public void testNaming() throws Exception { - this.context.register(FooConfig.class); - TestPropertyValues.of("foo.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(2); - assertThat(map.get("name")).isEqualTo("foo"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(FooConfig.class) + .withPropertyValues("foo.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(2); + assertThat(map.get("name")).isEqualTo("foo"); + }); } @Test @SuppressWarnings("unchecked") public void testNestedNaming() throws Exception { - this.context.register(FooConfig.class); - TestPropertyValues.of("foo.bar.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(2); - assertThat(((Map) map.get("bar")).get("name")).isEqualTo("foo"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(FooConfig.class) + .withPropertyValues("foo.bar.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(2); + assertThat(((Map) map.get("bar")).get("name")) + .isEqualTo("foo"); + }); } @Test @SuppressWarnings("unchecked") public void testCycle() throws Exception { - this.context.register(CycleConfig.class); - TestPropertyValues.of("foo.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(1); - assertThat(map.get("error")).isEqualTo("Cannot serialize 'foo'"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(CycleConfig.class) + .withPropertyValues("foo.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(1); + assertThat(map.get("error")).isEqualTo("Cannot serialize 'foo'"); + }); } @Test @SuppressWarnings("unchecked") public void testMap() throws Exception { - this.context.register(MapConfig.class); - TestPropertyValues.of("foo.map.name:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(3); - assertThat(((Map) map.get("map")).get("name")).isEqualTo("foo"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(MapConfig.class) + .withPropertyValues("foo.map.name:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(3); + assertThat(((Map) map.get("map")).get("name")) + .isEqualTo("foo"); + }); } @Test @SuppressWarnings("unchecked") public void testEmptyMapIsNotAdded() throws Exception { - this.context.register(MapConfig.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - System.err.println(nestedProperties); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(2); - assertThat(map).doesNotContainKey("map"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(MapConfig.class); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + System.err.println(nestedProperties); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(2); + assertThat(map).doesNotContainKey("map"); + }); } @Test @SuppressWarnings("unchecked") public void testList() throws Exception { - this.context.register(ListConfig.class); - TestPropertyValues.of("foo.list[0]:foo").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(3); - assertThat(((List) map.get("list")).get(0)).isEqualTo("foo"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(ListConfig.class) + .withPropertyValues("foo.list[0]:foo"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(3); + assertThat(((List) map.get("list")).get(0)).isEqualTo("foo"); + }); } @Test @SuppressWarnings("unchecked") public void testInetAddress() throws Exception { - this.context.register(AddressedConfig.class); - TestPropertyValues.of("foo.address:192.168.1.10").applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).isNotNull(); - System.err.println(nestedProperties); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map map = (Map) nestedProperties - .get("properties"); - assertThat(map).isNotNull(); - assertThat(map).hasSize(3); - assertThat(map.get("address")).isEqualTo("192.168.1.10"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(AddressedConfig.class) + .withPropertyValues("foo.address:192.168.1.10"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).isNotNull(); + System.err.println(nestedProperties); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map map = (Map) nestedProperties + .get("properties"); + assertThat(map).isNotNull(); + assertThat(map).hasSize(3); + assertThat(map.get("address")).isEqualTo("192.168.1.10"); + }); } @Test @SuppressWarnings("unchecked") + public void testInitializedMapAndList() throws Exception { - this.context.register(InitializedMapAndListPropertiesConfig.class); - TestPropertyValues.of("foo.map.entryOne:true", "foo.list[0]:abc") - .applyTo(this.context); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = this.context - .getBean(ConfigurationPropertiesReportEndpoint.class); - Map properties = report.invoke(); - assertThat(properties).containsKeys("foo"); - Map nestedProperties = (Map) properties - .get("foo"); - assertThat(nestedProperties).containsOnlyKeys("prefix", "properties"); - assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); - Map propertiesMap = (Map) nestedProperties - .get("properties"); - assertThat(propertiesMap).containsOnlyKeys("bar", "name", "map", "list"); - Map map = (Map) propertiesMap.get("map"); - assertThat(map).containsOnly(entry("entryOne", true)); - List list = (List) propertiesMap.get("list"); - assertThat(list).containsExactly("abc"); + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(InitializedMapAndListPropertiesConfig.class) + .withPropertyValues("foo.map.entryOne:true", "foo.list[0]:abc"); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + assertThat(properties).containsKeys("foo"); + Map nestedProperties = (Map) properties + .get("foo"); + assertThat(nestedProperties).containsOnlyKeys("prefix", "properties"); + assertThat(nestedProperties.get("prefix")).isEqualTo("foo"); + Map propertiesMap = (Map) nestedProperties + .get("properties"); + assertThat(propertiesMap).containsOnlyKeys("bar", "name", "map", "list"); + Map map = (Map) propertiesMap.get("map"); + assertThat(map).containsOnly(entry("entryOne", true)); + List list = (List) propertiesMap.get("list"); + assertThat(list).containsExactly("abc"); + }); } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointTests.java index 74f482bf31..02e870c7f6 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointTests.java @@ -18,18 +18,20 @@ package org.springframework.boot.actuate.endpoint; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.junit.Test; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -37,211 +39,169 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ConfigurationPropertiesReportEndpoint}. * * @author Dave Syer + * @author Andy Wilkinson */ -public class ConfigurationPropertiesReportEndpointTests - extends AbstractEndpointTests { - - public ConfigurationPropertiesReportEndpointTests() { - super(Config.class, ConfigurationPropertiesReportEndpoint.class, "configprops", - "endpoints.configprops"); - } - - @Test - public void testInvoke() throws Exception { - assertThat(getEndpointBean().invoke().size()).isGreaterThan(0); - } +public class ConfigurationPropertiesReportEndpointTests { @Test @SuppressWarnings("unchecked") - public void testNaming() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("testProperties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("prefix")).isEqualTo("test"); - assertThat(nestedProperties.get("properties")).isNotNull(); - + public void configurationPropertiesAreReturned() throws Exception { + load((properties) -> { + assertThat(properties.size()).isGreaterThan(0); + Map nestedProperties = (Map) properties + .get("testProperties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("prefix")).isEqualTo("test"); + assertThat(nestedProperties.get("properties")).isNotNull(); + }); } @Test @SuppressWarnings("unchecked") public void entriesWithNullValuesAreNotIncluded() { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) properties - .get("testProperties"); - assertThat((Map) nestedProperties.get("properties")) - .doesNotContainKey("nullValue"); + load((properties) -> { + Map nestedProperties = (Map) properties + .get("testProperties"); + assertThat((Map) nestedProperties.get("properties")) + .doesNotContainKey("nullValue"); + }); } @Test @SuppressWarnings("unchecked") - public void testDefaultKeySanitization() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + public void defaultKeySanitization() throws Exception { + load((properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); + assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + }); } @Test @SuppressWarnings("unchecked") - public void testKeySanitization() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - report.setKeysToSanitize("property"); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("123456"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); - } - - @Test - @SuppressWarnings("unchecked") - public void testKeySanitizationWithList() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - report.setKeysToSanitize("property"); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("123456"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); + public void customKeySanitization() throws Exception { + load("property", (properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("dbPassword")).isEqualTo("123456"); + assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); + }); } @SuppressWarnings("unchecked") @Test - public void testKeySanitizationWithCustomPattern() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - report.setKeysToSanitize(".*pass.*"); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + public void customPatternKeySanitization() throws Exception { + load(".*pass.*", (properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); + assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + }); } @SuppressWarnings("unchecked") @Test - public void testKeySanitizationWithCustomKeysByEnvironment() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("endpoints.configprops.keys-to-sanitize:property") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("123456"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); - } - - @SuppressWarnings("unchecked") - @Test - public void testKeySanitizationWithCustomPatternByEnvironment() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("endpoints.configprops.keys-to-sanitize: .*pass.*") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("654321"); + public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception { + ApplicationContextRunner tester = new ApplicationContextRunner() + .withPropertyValues( + "endpoints.configprops.keys-to-sanitize: .*pass.*, property") + .withUserConfiguration(Config.class); + tester.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = endpoint.configurationProperties(); + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); + assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); + }); } @Test @SuppressWarnings("unchecked") - public void testKeySanitizationWithCustomPatternAndKeyByEnvironment() - throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("endpoints.configprops.keys-to-sanitize: .*pass.*, property") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - assertThat(nestedProperties.get("dbPassword")).isEqualTo("******"); - assertThat(nestedProperties.get("myTestProperty")).isEqualTo("******"); - } - - @Test - @SuppressWarnings("unchecked") - public void testKeySanitizationWithCustomPatternUsingCompositeKeys() - throws Exception { + public void keySanitizationWithCustomPatternUsingCompositeKeys() throws Exception { // gh-4415 - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("endpoints.configprops.keys-to-sanitize: .*\\.secrets\\..*, .*\\.hidden\\..*") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties).isNotNull(); - Map secrets = (Map) nestedProperties - .get("secrets"); - Map hidden = (Map) nestedProperties.get("hidden"); - assertThat(secrets.get("mine")).isEqualTo("******"); - assertThat(secrets.get("yours")).isEqualTo("******"); - assertThat(hidden.get("mine")).isEqualTo("******"); + load(Arrays.asList(".*\\.secrets\\..*", ".*\\.hidden\\..*"), (properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties).isNotNull(); + Map secrets = (Map) nestedProperties + .get("secrets"); + Map hidden = (Map) nestedProperties + .get("hidden"); + assertThat(secrets.get("mine")).isEqualTo("******"); + assertThat(secrets.get("yours")).isEqualTo("******"); + assertThat(hidden.get("mine")).isEqualTo("******"); + }); } @Test @SuppressWarnings("unchecked") public void mixedBoolean() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties.get("mixedBoolean")).isEqualTo(true); + load((properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties.get("mixedBoolean")).isEqualTo(true); + }); } @Test @SuppressWarnings("unchecked") public void listsAreSanitized() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties.get("listItems")).isInstanceOf(List.class); - List list = (List) nestedProperties.get("listItems"); - assertThat(list).hasSize(1); - Map item = (Map) list.get(0); - assertThat(item.get("somePassword")).isEqualTo("******"); + load((properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties.get("listItems")).isInstanceOf(List.class); + List list = (List) nestedProperties.get("listItems"); + assertThat(list).hasSize(1); + Map item = (Map) list.get(0); + assertThat(item.get("somePassword")).isEqualTo("******"); + }); } @Test @SuppressWarnings("unchecked") public void listsOfListsAreSanitized() throws Exception { - ConfigurationPropertiesReportEndpoint report = getEndpointBean(); - Map properties = report.invoke(); - Map nestedProperties = (Map) ((Map) properties - .get("testProperties")).get("properties"); - assertThat(nestedProperties.get("listOfListItems")).isInstanceOf(List.class); - List> listOfLists = (List>) nestedProperties - .get("listOfListItems"); - assertThat(listOfLists).hasSize(1); - List list = listOfLists.get(0); - assertThat(list).hasSize(1); - Map item = (Map) list.get(0); - assertThat(item.get("somePassword")).isEqualTo("******"); + load((properties) -> { + Map nestedProperties = (Map) ((Map) properties + .get("testProperties")).get("properties"); + assertThat(nestedProperties.get("listOfListItems")).isInstanceOf(List.class); + List> listOfLists = (List>) nestedProperties + .get("listOfListItems"); + assertThat(listOfLists).hasSize(1); + List list = listOfLists.get(0); + assertThat(list).hasSize(1); + Map item = (Map) list.get(0); + assertThat(item.get("somePassword")).isEqualTo("******"); + }); + } + + private void load(Consumer> properties) { + load(Collections.emptyList(), properties); + } + + private void load(String keyToSanitize, Consumer> properties) { + load(Collections.singletonList(keyToSanitize), properties); + } + + private void load(List keysToSanitize, + Consumer> properties) { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(Config.class); + contextRunner.run((context) -> { + ConfigurationPropertiesReportEndpoint endpoint = context + .getBean(ConfigurationPropertiesReportEndpoint.class); + if (!CollectionUtils.isEmpty(keysToSanitize)) { + endpoint.setKeysToSanitize( + keysToSanitize.toArray(new String[keysToSanitize.size()])); + } + properties.accept(endpoint.configurationProperties()); + }); } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/DumpEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/DumpEndpointTests.java deleted file mode 100644 index 170560dec7..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/DumpEndpointTests.java +++ /dev/null @@ -1,58 +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.lang.management.ThreadInfo; -import java.util.List; - -import org.junit.Test; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link DumpEndpoint}. - * - * @author Phillip Webb - */ -public class DumpEndpointTests extends AbstractEndpointTests { - - public DumpEndpointTests() { - super(Config.class, DumpEndpoint.class, "dump", "endpoints.dump"); - } - - @Test - public void invoke() throws Exception { - List threadInfo = getEndpointBean().invoke(); - assertThat(threadInfo.size()).isGreaterThan(0); - } - - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public DumpEndpoint endpoint() { - return new DumpEndpoint(); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java index ef7d6a4272..4209dd5cab 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java @@ -26,14 +26,11 @@ import org.junit.Test; 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.EnableConfigurationProperties; import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.StandardEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -47,48 +44,45 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Madhura Bhave * @author Andy Wilkinson */ -public class EnvironmentEndpointTests extends AbstractEndpointTests { +public class EnvironmentEndpointTests { - public EnvironmentEndpointTests() { - super(Config.class, EnvironmentEndpoint.class, "env", "endpoints.env"); - } - - @Override @After public void close() { System.clearProperty("VCAP_SERVICES"); } @Test - public void invoke() { - EnvironmentDescriptor env = getEndpointBean().invoke(); + public void basicResponse() { + EnvironmentDescriptor env = new EnvironmentEndpoint(new StandardEnvironment()) + .environment(null); assertThat(env.getActiveProfiles()).isEmpty(); assertThat(env.getPropertySources()).hasSize(2); } @Test - public void testCompositeSource() { - EnvironmentEndpoint report = getEndpointBean(); + public void compositeSourceIsHandledCorrectly() { + StandardEnvironment environment = new StandardEnvironment(); CompositePropertySource source = new CompositePropertySource("composite"); source.addPropertySource(new MapPropertySource("one", Collections.singletonMap("foo", (Object) "bar"))); source.addPropertySource(new MapPropertySource("two", Collections.singletonMap("foo", (Object) "spam"))); - this.context.getEnvironment().getPropertySources().addFirst(source); - EnvironmentDescriptor env = report.invoke(); + environment.getPropertySources().addFirst(source); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); assertThat(getSource("composite:one", env).getProperties().get("foo").getValue()) .isEqualTo("bar"); } @Test - public void testKeySanitization() { + public void sensitiveKeysHaveTheirValuesSanitized() { System.setProperty("dbPassword", "123456"); System.setProperty("apiKey", "123456"); System.setProperty("mySecret", "123456"); System.setProperty("myCredentials", "123456"); System.setProperty("VCAP_SERVICES", "123456"); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); + EnvironmentDescriptor env = new EnvironmentEndpoint(new StandardEnvironment()) + .environment(null); Map systemProperties = getSource( "systemProperties", env).getProperties(); assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); @@ -101,13 +95,13 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests systemProperties = getSource( "systemProperties", env).getProperties(); assertThat( @@ -126,12 +120,12 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests systemProperties = getSource( "systemProperties", env).getProperties(); assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456"); @@ -140,12 +134,12 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests systemProperties = getSource( "systemProperties", env).getProperties(); assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); @@ -153,131 +147,60 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests systemProperties = getSource( - "systemProperties", env).getProperties(); - assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("123456"); - assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******"); - clearSystemProperties("dbPassword", "apiKey"); - } - - @Test - public void testKeySanitizationWithCustomPatternByEnvironment() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("endpoints.env.keys-to-sanitize: .*pass.*") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - System.setProperty("dbPassword", "123456"); - System.setProperty("apiKey", "123456"); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map systemProperties = getSource( - "systemProperties", env).getProperties(); - assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); - assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("123456"); - clearSystemProperties("dbPassword", "apiKey"); - } - - @Test - public void testKeySanitizationWithCustomPatternAndKeyByEnvironment() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("endpoints.env.keys-to-sanitize: .*pass.*, key") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - System.setProperty("dbPassword", "123456"); - System.setProperty("apiKey", "123456"); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map systemProperties = getSource( - "systemProperties", env).getProperties(); - assertThat(systemProperties.get("dbPassword").getValue()).isEqualTo("******"); - assertThat(systemProperties.get("apiKey").getValue()).isEqualTo("******"); - clearSystemProperties("dbPassword", "apiKey"); - } - @Test public void propertyWithPlaceholderResolved() { - this.context = new AnnotationConfigApplicationContext(); + StandardEnvironment environment = new StandardEnvironment(); TestPropertyValues.of("my.foo: ${bar.blah}", "bar.blah: hello") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map testProperties = getSource("test", env) - .getProperties(); - assertThat(testProperties.get("my.foo").getValue()).isEqualTo("hello"); + .applyTo(environment); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); + assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) + .isEqualTo("hello"); } @Test public void propertyWithPlaceholderNotResolved() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map testProperties = getSource("test", env) - .getProperties(); - assertThat(testProperties.get("my.foo").getValue()).isEqualTo("${bar.blah}"); + StandardEnvironment environment = new StandardEnvironment(); + TestPropertyValues.of("my.foo: ${bar.blah}").applyTo(environment); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); + assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) + .isEqualTo("${bar.blah}"); } @Test public void propertyWithSensitivePlaceholderResolved() { - this.context = new AnnotationConfigApplicationContext(); + StandardEnvironment environment = new StandardEnvironment(); TestPropertyValues .of("my.foo: http://${bar.password}://hello", "bar.password: hello") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map testProperties = getSource("test", env) - .getProperties(); - assertThat(testProperties.get("my.foo").getValue()) + .applyTo(environment); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); + assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) .isEqualTo("http://******://hello"); } @Test public void propertyWithSensitivePlaceholderNotResolved() { - this.context = new AnnotationConfigApplicationContext(); + StandardEnvironment environment = new StandardEnvironment(); TestPropertyValues.of("my.foo: http://${bar.password}://hello") - .applyTo(this.context); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); - Map testProperties = getSource("test", env) - .getProperties(); - assertThat(testProperties.get("my.foo").getValue()) + .applyTo(environment); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); + assertThat(getSource("test", env).getProperties().get("my.foo").getValue()) .isEqualTo("http://${bar.password}://hello"); } @Test @SuppressWarnings("unchecked") public void propertyWithTypeOtherThanStringShouldNotFail() { - this.context = new AnnotationConfigApplicationContext(); - MutablePropertySources propertySources = this.context.getEnvironment() - .getPropertySources(); - Map source = new HashMap<>(); + StandardEnvironment environment = new StandardEnvironment(); + MutablePropertySources propertySources = environment.getPropertySources(); + Map source = new HashMap(); source.put("foo", Collections.singletonMap("bar", "baz")); propertySources.addFirst(new MapPropertySource("test", source)); - this.context.register(Config.class); - this.context.refresh(); - EnvironmentEndpoint report = getEndpointBean(); - EnvironmentDescriptor env = report.invoke(); + EnvironmentDescriptor env = new EnvironmentEndpoint(environment) + .environment(null); Map testProperties = getSource("test", env) .getProperties(); Map foo = (Map) testProperties.get("foo") @@ -297,15 +220,4 @@ public class EnvironmentEndpointTests extends AbstractEndpointTests name.equals(source.getName())).findFirst().get(); } - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public EnvironmentEndpoint endpoint() { - return new EnvironmentEndpoint(); - } - - } - } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/FlywayEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/FlywayEndpointTests.java index 7705b72b0d..23a50fe01a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/FlywayEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/FlywayEndpointTests.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -33,16 +34,16 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link FlywayEndpoint}. * * @author EddĂș MelĂ©ndez + * @author Andy Wilkinson */ -public class FlywayEndpointTests extends AbstractEndpointTests { - - public FlywayEndpointTests() { - super(Config.class, FlywayEndpoint.class, "flyway", "endpoints.flyway"); - } +public class FlywayEndpointTests { @Test - public void invoke() throws Exception { - assertThat(getEndpointBean().invoke()).hasSize(1); + public void flywayReportIsProduced() throws Exception { + new ApplicationContextRunner().withUserConfiguration(Config.class) + .run((context) -> assertThat( + context.getBean(FlywayEndpoint.class).flywayReports()) + .hasSize(1)); } @Configuration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java index 766eb6cd5f..72a21a8470 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java @@ -16,18 +16,15 @@ package org.springframework.boot.actuate.endpoint; +import java.util.HashMap; import java.util.Map; import org.junit.Test; 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.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -36,39 +33,39 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Phillip Webb * @author Christian Dupuis + * @author Andy Wilkinson */ -public class HealthEndpointTests extends AbstractEndpointTests { +public class HealthEndpointTests { - public HealthEndpointTests() { - super(Config.class, HealthEndpoint.class, "health", "endpoints.health"); + @Test + public void upAndUpIsAggregatedToUp() throws Exception { + Map healthIndicators = new HashMap<>(); + healthIndicators.put("up", () -> new Health.Builder().status(Status.UP).build()); + healthIndicators.put("upAgain", + () -> new Health.Builder().status(Status.UP).build()); + HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), + healthIndicators); + assertThat(endpoint.health().getStatus()).isEqualTo(Status.UP); } @Test - public void invoke() throws Exception { - // As FINE isn't configured in the order we get UNKNOWN - assertThat(getEndpointBean().invoke().getStatus()).isEqualTo(Status.UNKNOWN); + public void upAndDownIsAggregatedToDown() throws Exception { + Map healthIndicators = new HashMap<>(); + healthIndicators.put("up", () -> new Health.Builder().status(Status.UP).build()); + healthIndicators.put("down", + () -> new Health.Builder().status(Status.DOWN).build()); + HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), + healthIndicators); + assertThat(endpoint.health().getStatus()).isEqualTo(Status.DOWN); } - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public HealthEndpoint endpoint(HealthAggregator healthAggregator, - Map healthIndicators) { - return new HealthEndpoint(healthAggregator, healthIndicators); - } - - @Bean - public HealthIndicator statusHealthIndicator() { - return () -> new Health.Builder().status("FINE").build(); - } - - @Bean - public HealthAggregator healthAggregator() { - return new OrderedHealthAggregator(); - } - + @Test + public void unknownStatusMapsToUnknown() throws Exception { + Map healthIndicators = new HashMap<>(); + healthIndicators.put("status", () -> new Health.Builder().status("FINE").build()); + HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), + healthIndicators); + assertThat(endpoint.health().getStatus()).isEqualTo(Status.UNKNOWN); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java index 2181c6569b..333fde842b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java @@ -16,16 +16,12 @@ package org.springframework.boot.actuate.endpoint; -import java.util.List; +import java.util.Arrays; +import java.util.Collections; import java.util.Map; import org.junit.Test; -import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -34,33 +30,26 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb * @author Dave Syer * @author Meang Akira Tanaka + * @author Andy Wilkinson */ -public class InfoEndpointTests extends AbstractEndpointTests { +public class InfoEndpointTests { - public InfoEndpointTests() { - super(Config.class, InfoEndpoint.class, "info", "endpoints.info"); + @Test + public void info() { + InfoEndpoint endpoint = new InfoEndpoint( + Arrays.asList((builder) -> builder.withDetail("key1", "value1"), + (builder) -> builder.withDetail("key2", "value2"))); + Map info = endpoint.info(); + assertThat(info).hasSize(2); + assertThat(info).containsEntry("key1", "value1"); + assertThat(info).containsEntry("key2", "value2"); } @Test - public void invoke() throws Exception { - Map actual = getEndpointBean().invoke(); - assertThat(actual.get("key1")).isEqualTo("value1"); - } - - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public InfoContributor infoContributor() { - return (builder) -> builder.withDetail("key1", "value1"); - } - - @Bean - public InfoEndpoint endpoint(List infoContributors) { - return new InfoEndpoint(infoContributors); - } - + public void infoWithNoContributorsProducesEmptyMap() { + InfoEndpoint endpoint = new InfoEndpoint(Collections.emptyList()); + Map info = endpoint.info(); + assertThat(info).isEmpty(); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpointTests.java index 97eb5f018c..1ace1a7622 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpointTests.java @@ -16,14 +16,14 @@ package org.springframework.boot.actuate.endpoint; +import java.util.Map; + import liquibase.integration.spring.SpringLiquibase; import org.junit.Test; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -34,62 +34,36 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link LiquibaseEndpoint}. * * @author EddĂș MelĂ©ndez + * @author Andy Wilkinson */ -public class LiquibaseEndpointTests extends AbstractEndpointTests { - - public LiquibaseEndpointTests() { - super(Config.class, LiquibaseEndpoint.class, "liquibase", "endpoints.liquibase"); - } +public class LiquibaseEndpointTests { @Test - public void invoke() throws Exception { - assertThat(getEndpointBean().invoke()).hasSize(1); + public void liquibaseReportIsReturned() throws Exception { + new ApplicationContextRunner().withUserConfiguration(Config.class) + .run((context) -> assertThat( + context.getBean(LiquibaseEndpoint.class).liquibaseReports()) + .hasSize(1)); } @Test public void invokeWithCustomSchema() throws Exception { - this.context.close(); - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues - .of("spring.liquibase.default-schema=CUSTOMSCHEMA", + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(Config.class) + .withPropertyValues("liquibase.default-schema=CUSTOMSCHEMA", "spring.datasource.generate-unique-name=true", - "spring.datasource.schema=classpath:/db/create-custom-schema.sql") - .applyTo(this.context); - this.context.register(CustomSchemaConfig.class); - this.context.refresh(); - assertThat(getEndpointBean().invoke()).hasSize(1); + "spring.datasource.schema=classpath:/db/create-custom-schema.sql"); + contextRunner.run((context) -> assertThat( + context.getBean(LiquibaseEndpoint.class).liquibaseReports()).hasSize(1)); } @Configuration @Import({ EmbeddedDataSourceConfiguration.class, LiquibaseAutoConfiguration.class }) public static class Config { - private final SpringLiquibase liquibase; - - public Config(SpringLiquibase liquibase) { - this.liquibase = liquibase; - } - @Bean - public LiquibaseEndpoint endpoint() { - return new LiquibaseEndpoint(this.liquibase); - } - - } - - @Configuration - @Import({ DataSourceAutoConfiguration.class, LiquibaseAutoConfiguration.class }) - public static class CustomSchemaConfig { - - private final SpringLiquibase liquibase; - - public CustomSchemaConfig(SpringLiquibase liquibase) { - this.liquibase = liquibase; - } - - @Bean - public LiquibaseEndpoint endpoint() { - return new LiquibaseEndpoint(this.liquibase); + public LiquibaseEndpoint endpoint(Map liquibases) { + return new LiquibaseEndpoint(liquibases); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java index 6a32926595..6146a5f51a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/LoggersEndpointTests.java @@ -24,12 +24,9 @@ import java.util.Set; import org.junit.Test; import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingSystem; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -40,21 +37,20 @@ import static org.mockito.Mockito.verify; * Tests for {@link LoggersEndpoint}. * * @author Ben Hale + * @author Andy Wilkinson */ -public class LoggersEndpointTests extends AbstractEndpointTests { +public class LoggersEndpointTests { - public LoggersEndpointTests() { - super(Config.class, LoggersEndpoint.class, "loggers", "endpoints.loggers"); - } + private final LoggingSystem loggingSystem = mock(LoggingSystem.class); @Test @SuppressWarnings("unchecked") - public void invokeShouldReturnConfigurations() throws Exception { - given(getLoggingSystem().getLoggerConfigurations()).willReturn(Collections + public void loggersShouldReturnLoggerConfigurations() throws Exception { + given(this.loggingSystem.getLoggerConfigurations()).willReturn(Collections .singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); - given(getLoggingSystem().getSupportedLogLevels()) + given(this.loggingSystem.getSupportedLogLevels()) .willReturn(EnumSet.allOf(LogLevel.class)); - Map result = getEndpointBean().invoke(); + Map result = new LoggersEndpoint(this.loggingSystem).loggers(); Map loggers = (Map) result .get("loggers"); Set levels = (Set) result.get("levels"); @@ -66,44 +62,25 @@ public class LoggersEndpointTests extends AbstractEndpointTests } @Test - public void invokeWhenNameSpecifiedShouldReturnLevels() throws Exception { - given(getLoggingSystem().getLoggerConfiguration("ROOT")) + public void loggerLevelsWhenNameSpecifiedShouldReturnLevels() throws Exception { + given(this.loggingSystem.getLoggerConfiguration("ROOT")) .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); - LoggerLevels levels = getEndpointBean().invoke("ROOT"); + LoggerLevels levels = new LoggersEndpoint(this.loggingSystem) + .loggerLevels("ROOT"); assertThat(levels.getConfiguredLevel()).isNull(); assertThat(levels.getEffectiveLevel()).isEqualTo("DEBUG"); } @Test - public void setLogLevelShouldSetLevelOnLoggingSystem() throws Exception { - getEndpointBean().setLogLevel("ROOT", LogLevel.DEBUG); - verify(getLoggingSystem()).setLogLevel("ROOT", LogLevel.DEBUG); + public void configureLogLevelShouldSetLevelOnLoggingSystem() throws Exception { + new LoggersEndpoint(this.loggingSystem).configureLogLevel("ROOT", LogLevel.DEBUG); + verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); } @Test - public void setLogLevelToNull() { - getEndpointBean().setLogLevel("ROOT", null); - verify(getLoggingSystem()).setLogLevel("ROOT", null); - } - - private LoggingSystem getLoggingSystem() { - return this.context.getBean(LoggingSystem.class); - } - - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public LoggingSystem loggingSystem() { - return mock(LoggingSystem.class); - } - - @Bean - public LoggersEndpoint endpoint(LoggingSystem loggingSystem) { - return new LoggersEndpoint(loggingSystem); - } - + public void configureLogLevelWithNullSetsLevelOnLoggingSystemToNull() { + new LoggersEndpoint(this.loggingSystem).configureLogLevel("ROOT", null); + verify(this.loggingSystem).setLogLevel("ROOT", null); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java index 9cb821f6a4..041a852ccd 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java @@ -16,21 +16,15 @@ package org.springframework.boot.actuate.endpoint; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import org.junit.Test; import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import static org.assertj.core.api.Assertions.assertThat; @@ -39,8 +33,9 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link MetricsEndpoint}. * * @author Phillip Webb + * @author Andy Wilkinson */ -public class MetricsEndpointTests extends AbstractEndpointTests { +public class MetricsEndpointTests { private Metric metric1 = new Metric<>("a", 1); @@ -48,27 +43,62 @@ public class MetricsEndpointTests extends AbstractEndpointTests private Metric metric3 = new Metric<>("c", 3); - public MetricsEndpointTests() { - super(Config.class, MetricsEndpoint.class, "metrics", "endpoints.metrics"); + @Test + public void basicMetrics() throws Exception { + MetricsEndpoint endpoint = new MetricsEndpoint( + Collections.singletonList(() -> Arrays.asList(new Metric("a", 5), + new Metric("b", 4)))); + Map metrics = endpoint.metrics(null); + assertThat(metrics.get("a")).isEqualTo(5); + assertThat(metrics.get("b")).isEqualTo(4); } @Test - public void invoke() throws Exception { - assertThat(getEndpointBean().invoke().get("a")).isEqualTo(0.5f); + public void metricsOrderingIsReflectedInOutput() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + Map metrics = new MetricsEndpoint(publicMetrics).metrics(null); + assertThat(metrics.keySet()).containsExactly("a", "b", "c"); + assertThat(metrics).hasSize(3); } @Test - public void ordered() { - List publicMetrics = new ArrayList<>(); - publicMetrics - .add(new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3)); - publicMetrics.add(new TestPublicMetrics(1, this.metric1)); - Map metrics = new MetricsEndpoint(publicMetrics).invoke(); - Iterator> iterator = metrics.entrySet().iterator(); - assertThat(iterator.next().getKey()).isEqualTo("a"); - assertThat(iterator.next().getKey()).isEqualTo("b"); - assertThat(iterator.next().getKey()).isEqualTo("c"); - assertThat(iterator.hasNext()).isFalse(); + public void singleSelectedMetric() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + Map selected = new MetricsEndpoint(publicMetrics) + .metricNamed("a"); + assertThat(selected).hasSize(1); + assertThat(selected).containsEntry("a", 1); + } + + @Test + public void multipleSelectedMetrics() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + Map selected = new MetricsEndpoint(publicMetrics).metrics("[ab]"); + assertThat(selected).hasSize(2); + assertThat(selected).containsEntry("a", 1); + assertThat(selected).containsEntry("b", 2); + } + + @Test + public void noMetricMatchingName() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + assertThat(new MetricsEndpoint(publicMetrics).metricNamed("z")).isNull(); + } + + @Test + public void noMetricMatchingPattern() { + List publicMetrics = Arrays.asList( + new TestPublicMetrics(2, this.metric2, this.metric2, this.metric3), + new TestPublicMetrics(1, this.metric1)); + assertThat(new MetricsEndpoint(publicMetrics).metrics("[z]")).isEmpty(); } private static class TestPublicMetrics implements PublicMetrics, Ordered { @@ -94,16 +124,4 @@ public class MetricsEndpointTests extends AbstractEndpointTests } - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public MetricsEndpoint endpoint() { - Metric metric = new Metric<>("a", 0.5f); - return new MetricsEndpoint(() -> Collections.singleton(metric)); - } - - } - } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/NamePatternFilterTests.java similarity index 98% rename from spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java rename to spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/NamePatternFilterTests.java index 582c973444..473e7ca6d7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NamePatternFilterTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/NamePatternFilterTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint.mvc; +package org.springframework.boot.actuate.endpoint; import java.util.Map; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java index 29b251a17c..5c0d425359 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 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. @@ -16,14 +16,17 @@ package org.springframework.boot.actuate.endpoint; -import java.util.Arrays; import java.util.Collections; import java.util.Map; import org.junit.Test; -import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; -import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; +import org.springframework.boot.endpoint.EndpointInfo; +import org.springframework.boot.endpoint.EndpointOperationType; +import org.springframework.boot.endpoint.web.OperationRequestPredicate; +import org.springframework.boot.endpoint.web.WebEndpointHttpMethod; +import org.springframework.boot.endpoint.web.WebEndpointOperation; +import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -54,7 +57,7 @@ public class RequestMappingEndpointTests { mapping.initApplicationContext(); this.endpoint.setHandlerMappings( Collections.singletonList(mapping)); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(1); @SuppressWarnings("unchecked") Map map = (Map) result.get("/foo"); @@ -70,7 +73,7 @@ public class RequestMappingEndpointTests { mapping.initApplicationContext(); context.getDefaultListableBeanFactory().registerSingleton("mapping", mapping); this.endpoint.setApplicationContext(context); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(1); @SuppressWarnings("unchecked") Map map = (Map) result.get("/foo"); @@ -82,7 +85,7 @@ public class RequestMappingEndpointTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( MappingConfiguration.class); this.endpoint.setApplicationContext(context); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(1); @SuppressWarnings("unchecked") Map map = (Map) result.get("/foo"); @@ -93,20 +96,17 @@ public class RequestMappingEndpointTests { @Test public void beanMethodMappings() { StaticApplicationContext context = new StaticApplicationContext(); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(new EndpointMvcAdapter(new DumpEndpoint()))); - mapping.setApplicationContext(new StaticApplicationContext()); - mapping.afterPropertiesSet(); - context.getDefaultListableBeanFactory().registerSingleton("mapping", mapping); + context.getDefaultListableBeanFactory().registerSingleton("mapping", + createHandlerMapping()); this.endpoint.setApplicationContext(context); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(2); assertThat(result.keySet()) - .filteredOn((key) -> key.contains("/dump || /dump.json")) + .filteredOn((key) -> key.contains("[/application/test]")) .hasOnlyOneElementSatisfying( (key) -> assertThat((Map) result.get(key)) .containsOnlyKeys("bean", "method")); - assertThat(result.keySet()).filteredOn((key) -> key.contains(" || /.json")) + assertThat(result.keySet()).filteredOn((key) -> key.contains("[/application]")) .hasOnlyOneElementSatisfying( (key) -> assertThat((Map) result.get(key)) .containsOnlyKeys("bean", "method")); @@ -115,25 +115,37 @@ public class RequestMappingEndpointTests { @SuppressWarnings("unchecked") @Test public void concreteMethodMappings() { - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(new EndpointMvcAdapter(new DumpEndpoint()))); - mapping.setApplicationContext(new StaticApplicationContext()); - mapping.afterPropertiesSet(); + WebEndpointServletHandlerMapping mapping = createHandlerMapping(); this.endpoint.setMethodMappings( Collections.>singletonList(mapping)); - Map result = this.endpoint.invoke(); + Map result = this.endpoint.mappings(); assertThat(result).hasSize(2); assertThat(result.keySet()) - .filteredOn((key) -> key.contains("/dump || /dump.json")) + .filteredOn((key) -> key.contains("[/application/test]")) .hasOnlyOneElementSatisfying( (key) -> assertThat((Map) result.get(key)) .containsOnlyKeys("method")); - assertThat(result.keySet()).filteredOn((key) -> key.contains(" || /.json")) + assertThat(result.keySet()).filteredOn((key) -> key.contains("[/application]")) .hasOnlyOneElementSatisfying( (key) -> assertThat((Map) result.get(key)) .containsOnlyKeys("method")); } + private WebEndpointServletHandlerMapping createHandlerMapping() { + OperationRequestPredicate requestPredicate = new OperationRequestPredicate("test", + WebEndpointHttpMethod.GET, Collections.singletonList("application/json"), + Collections.singletonList("application/json")); + WebEndpointOperation operation = new WebEndpointOperation( + EndpointOperationType.READ, (arguments) -> "Invoked", true, + requestPredicate, "test"); + WebEndpointServletHandlerMapping mapping = new WebEndpointServletHandlerMapping( + "application", Collections.singleton(new EndpointInfo<>("test", true, + Collections.singleton(operation)))); + mapping.setApplicationContext(new StaticApplicationContext()); + mapping.afterPropertiesSet(); + return mapping; + } + @Configuration protected static class MappingConfiguration { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java index ae7e8d7662..04fdfd4f5b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownEndpointTests.java @@ -24,8 +24,11 @@ import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; 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; @@ -39,41 +42,60 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Dave Syer * @author Andy Wilkinson */ -public class ShutdownEndpointTests extends AbstractEndpointTests { +public class ShutdownEndpointTests { - public ShutdownEndpointTests() { - super(Config.class, ShutdownEndpoint.class, "shutdown", "endpoints.shutdown"); - } - - @Override - public void isEnabledByDefault() throws Exception { - // Shutdown is dangerous so is disabled by default - assertThat(getEndpointBean().isEnabled()).isFalse(); + @Test + public void shutdown() throws Exception { + ApplicationContextRunner contexRunner = new ApplicationContextRunner() + .withUserConfiguration(EndpointConfig.class); + contexRunner.run((context) -> { + EndpointConfig config = context.getBean(EndpointConfig.class); + ClassLoader previousTccl = Thread.currentThread().getContextClassLoader(); + Map result; + Thread.currentThread().setContextClassLoader( + new URLClassLoader(new URL[0], getClass().getClassLoader())); + try { + result = context.getBean(ShutdownEndpoint.class).shutdown(); + } + finally { + Thread.currentThread().setContextClassLoader(previousTccl); + } + assertThat((String) result.get("message")).startsWith("Shutting down"); + assertThat(((ConfigurableApplicationContext) context).isActive()).isTrue(); + assertThat(config.latch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(config.threadContextClassLoader) + .isEqualTo(getClass().getClassLoader()); + }); } @Test - public void invoke() throws Exception { - Config config = this.context.getBean(Config.class); - ClassLoader previousTccl = Thread.currentThread().getContextClassLoader(); - Map result; - Thread.currentThread().setContextClassLoader( - new URLClassLoader(new URL[0], getClass().getClassLoader())); - try { - result = getEndpointBean().invoke(); - } - finally { - Thread.currentThread().setContextClassLoader(previousTccl); - } - assertThat((String) result.get("message")).startsWith("Shutting down"); - assertThat(this.context.isActive()).isTrue(); - assertThat(config.latch.await(10, TimeUnit.SECONDS)).isTrue(); - assertThat(config.threadContextClassLoader) - .isEqualTo(getClass().getClassLoader()); + public void shutdownChild() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder( + EmptyConfig.class).child(EndpointConfig.class) + .web(WebApplicationType.NONE).run(); + CountDownLatch latch = context.getBean(EndpointConfig.class).latch; + assertThat((String) context.getBean(ShutdownEndpoint.class).shutdown() + .get("message")).startsWith("Shutting down"); + assertThat(context.isActive()).isTrue(); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + } + + @Test + public void shutdownParent() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder( + EndpointConfig.class).child(EmptyConfig.class) + .web(WebApplicationType.NONE).run(); + CountDownLatch parentLatch = context.getBean(EndpointConfig.class).latch; + CountDownLatch childLatch = context.getBean(EmptyConfig.class).latch; + assertThat((String) context.getBean(ShutdownEndpoint.class).shutdown() + .get("message")).startsWith("Shutting down"); + assertThat(context.isActive()).isTrue(); + assertThat(parentLatch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(childLatch.await(10, TimeUnit.SECONDS)).isTrue(); } @Configuration - @EnableConfigurationProperties - public static class Config { + public static class EndpointConfig { private final CountDownLatch latch = new CountDownLatch(1); @@ -88,11 +110,22 @@ public class ShutdownEndpointTests extends AbstractEndpointTests listener() { return (event) -> { - this.threadContextClassLoader = Thread.currentThread() + EndpointConfig.this.threadContextClassLoader = Thread.currentThread() .getContextClassLoader(); - this.latch.countDown(); + EndpointConfig.this.latch.countDown(); }; + } + } + + @Configuration + public static class EmptyConfig { + + private final CountDownLatch latch = new CountDownLatch(1); + + @Bean + public ApplicationListener listener() { + return (event) -> EmptyConfig.this.latch.countDown(); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java deleted file mode 100644 index 2db08db4bb..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java +++ /dev/null @@ -1,103 +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.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.boot.WebApplicationType; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.ApplicationContextTestUtils; -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 static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ShutdownEndpoint}. - * - * @author Dave Syer - */ -public class ShutdownParentEndpointTests { - - private ConfigurableApplicationContext context; - - @After - public void close() { - ApplicationContextTestUtils.closeAll(this.context); - } - - @Test - public void shutdownChild() throws Exception { - this.context = new SpringApplicationBuilder(Config.class).child(Empty.class) - .web(WebApplicationType.NONE).run(); - CountDownLatch latch = this.context.getBean(Config.class).latch; - assertThat((String) getEndpointBean().invoke().get("message")) - .startsWith("Shutting down"); - assertThat(this.context.isActive()).isTrue(); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - @Test - public void shutdownParent() throws Exception { - this.context = new SpringApplicationBuilder(Empty.class).child(Config.class) - .web(WebApplicationType.NONE).run(); - CountDownLatch latch = this.context.getBean(Config.class).latch; - assertThat((String) getEndpointBean().invoke().get("message")) - .startsWith("Shutting down"); - assertThat(this.context.isActive()).isTrue(); - assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); - } - - private ShutdownEndpoint getEndpointBean() { - return this.context.getBean(ShutdownEndpoint.class); - } - - @Configuration - @EnableConfigurationProperties - public static class Config { - - private CountDownLatch latch = new CountDownLatch(1); - - @Bean - public ShutdownEndpoint endpoint() { - ShutdownEndpoint endpoint = new ShutdownEndpoint(); - endpoint.setEnabled(true); - return endpoint; - } - - @Bean - public ApplicationListener listener() { - return (event) -> this.latch.countDown(); - - } - - } - - @Configuration - public static class Empty { - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpointTests.java new file mode 100644 index 0000000000..7e1258598a --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ThreadDumpEndpointTests.java @@ -0,0 +1,36 @@ +/* + * 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 org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ThreadDumpEndpoint}. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class ThreadDumpEndpointTests { + + @Test + public void dumpThreads() throws Exception { + assertThat(new ThreadDumpEndpoint().threadDump().size()).isGreaterThan(0); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/TraceEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/TraceEndpointTests.java index 517d5f1207..438fa066d0 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/TraceEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/TraceEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 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. @@ -23,9 +23,6 @@ import org.junit.Test; import org.springframework.boot.actuate.trace.InMemoryTraceRepository; import org.springframework.boot.actuate.trace.Trace; import org.springframework.boot.actuate.trace.TraceRepository; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -33,30 +30,16 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link TraceEndpoint}. * * @author Phillip Webb + * @author Andy Wilkinson */ -public class TraceEndpointTests extends AbstractEndpointTests { - - public TraceEndpointTests() { - super(Config.class, TraceEndpoint.class, "trace", "endpoints.trace"); - } +public class TraceEndpointTests { @Test - public void invoke() throws Exception { - Trace trace = getEndpointBean().invoke().get(0); + public void trace() throws Exception { + TraceRepository repository = new InMemoryTraceRepository(); + repository.add(Collections.singletonMap("a", "b")); + Trace trace = new TraceEndpoint(repository).traces().get(0); assertThat(trace.getInfo().get("a")).isEqualTo("b"); } - @Configuration - @EnableConfigurationProperties - public static class Config { - - @Bean - public TraceEndpoint endpoint() { - TraceRepository repository = new InMemoryTraceRepository(); - repository.add(Collections.singletonMap("a", "b")); - return new TraceEndpoint(repository); - } - - } - } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtensionTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtensionTests.java new file mode 100644 index 0000000000..4619a66081 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpointExtensionTests.java @@ -0,0 +1,67 @@ +/* + * 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.Collections; +import java.util.Date; +import java.util.List; + +import org.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AuditEventsJmxEndpointExtension}. + * + * @author Andy Wilkinson + */ +public class AuditEventsJmxEndpointExtensionTests { + + private final AuditEventRepository repository = mock(AuditEventRepository.class); + + private final AuditEventsJmxEndpointExtension extension = new AuditEventsJmxEndpointExtension( + new AuditEventsEndpoint(this.repository)); + + private final AuditEvent event = new AuditEvent("principal", "type", + Collections.singletonMap("a", "alpha")); + + @Test + public void eventsWithDateAfter() { + Date date = new Date(); + given(this.repository.find(null, date, null)) + .willReturn(Collections.singletonList(this.event)); + List result = this.extension.eventsWithDateAfter(date); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + + @Test + public void eventsWithPrincipalAndDateAfter() { + Date date = new Date(); + given(this.repository.find("Joan", date, null)) + .willReturn(Collections.singletonList(this.event)); + List result = this.extension.eventsWithPrincipalAndDateAfter("Joan", + date); + assertThat(result).isEqualTo(Collections.singletonList(this.event)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java deleted file mode 100644 index 91136267fd..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java +++ /dev/null @@ -1,366 +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.SimpleDateFormat; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import javax.management.MBeanException; -import javax.management.MBeanInfo; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import org.springframework.beans.MutablePropertyValues; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.logging.logback.LogbackLoggingSystem; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.jmx.export.MBeanExporter; -import org.springframework.jmx.support.ObjectNameManager; -import org.springframework.util.ObjectUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; - -/** - * Tests for {@link EndpointMBeanExporter} - * - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Stephane Nicoll - */ -public class EndpointMBeanExporterTests { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - GenericApplicationContext context = null; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void testRegistrationOfOneEndpoint() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - MBeanInfo mbeanInfo = mbeanExporter.getServer() - .getMBeanInfo(getObjectName("endpoint1", this.context)); - assertThat(mbeanInfo).isNotNull(); - assertThat(mbeanInfo.getOperations().length).isEqualTo(2); - assertThat(mbeanInfo.getAttributes().length).isEqualTo(2); - } - - @Test - public void testSkipRegistrationOfDisabledEndpoint() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - MutablePropertyValues mpv = new MutablePropertyValues(); - mpv.add("enabled", Boolean.FALSE); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class, null, mpv)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .isRegistered(getObjectName("endpoint1", this.context))).isFalse(); - } - - @Test - public void testRegistrationOfEnabledEndpoint() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - MutablePropertyValues mpv = new MutablePropertyValues(); - mpv.add("enabled", Boolean.TRUE); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class, null, mpv)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .isRegistered(getObjectName("endpoint1", this.context))).isTrue(); - } - - @Test - public void testRegistrationTwoEndpoints() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.registerBeanDefinition("endpoint2", - new RootBeanDefinition(TestEndpoint2.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .getMBeanInfo(getObjectName("endpoint1", this.context))).isNotNull(); - assertThat(mbeanExporter.getServer() - .getMBeanInfo(getObjectName("endpoint2", this.context))).isNotNull(); - } - - @Test - public void testRegistrationWithDifferentDomain() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class, null, - new MutablePropertyValues( - Collections.singletonMap("domain", "test-domain")))); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer().getMBeanInfo( - getObjectName("test-domain", "endpoint1", false, this.context))) - .isNotNull(); - } - - @Test - public void testRegistrationWithDifferentDomainAndIdentity() throws Exception { - Map properties = new HashMap<>(); - properties.put("domain", "test-domain"); - properties.put("ensureUniqueRuntimeObjectNames", true); - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class, null, - new MutablePropertyValues(properties))); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer().getMBeanInfo( - getObjectName("test-domain", "endpoint1", true, this.context))) - .isNotNull(); - } - - @Test - public void testRegistrationWithDifferentDomainAndIdentityAndStaticNames() - throws Exception { - Map properties = new HashMap<>(); - properties.put("domain", "test-domain"); - properties.put("ensureUniqueRuntimeObjectNames", true); - Properties staticNames = new Properties(); - staticNames.put("key1", "value1"); - staticNames.put("key2", "value2"); - properties.put("objectNameStaticProperties", staticNames); - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class, null, - new MutablePropertyValues(properties))); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer().getMBeanInfo(ObjectNameManager.getInstance( - getObjectName("test-domain", "endpoint1", true, this.context).toString() - + ",key1=value1,key2=value2"))).isNotNull(); - } - - @Test - public void testRegistrationWithParentContext() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(TestEndpoint.class)); - GenericApplicationContext parent = new GenericApplicationContext(); - this.context.setParent(parent); - parent.refresh(); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - assertThat(mbeanExporter.getServer() - .getMBeanInfo(getObjectName("endpoint1", this.context))).isNotNull(); - parent.close(); - } - - @Test - public void jsonMapConversionWithDefaultObjectMapper() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(JsonMapConversionEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - Object response = mbeanExporter.getServer().invoke( - getObjectName("endpoint1", this.context), "getData", new Object[0], - new String[0]); - assertThat(response).isInstanceOf(Map.class); - assertThat(((Map) response).get("date")).isInstanceOf(Long.class); - } - - @Test - public void jsonMapConversionWithCustomObjectMapper() throws Exception { - this.context = new GenericApplicationContext(); - ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues(); - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); - constructorArgs.addIndexedArgumentValue(0, objectMapper); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class, constructorArgs, - null)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(JsonMapConversionEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - Object response = mbeanExporter.getServer().invoke( - getObjectName("endpoint1", this.context), "getData", new Object[0], - new String[0]); - assertThat(response).isInstanceOf(Map.class); - assertThat(((Map) response).get("date")).isInstanceOf(String.class); - } - - @Test - public void jsonListConversion() throws Exception { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - this.context.registerBeanDefinition("endpoint1", - new RootBeanDefinition(JsonListConversionEndpoint.class)); - this.context.refresh(); - MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); - Object response = mbeanExporter.getServer().invoke( - getObjectName("endpoint1", this.context), "getData", new Object[0], - new String[0]); - assertThat(response).isInstanceOf(List.class); - assertThat(((List) response).get(0)).isInstanceOf(Long.class); - } - - @Test - public void loggerEndpointLowerCaseLogLevel() throws Exception { - MBeanExporter mbeanExporter = registerLoggersEndpoint(); - Object response = mbeanExporter.getServer().invoke( - getObjectName("loggersEndpoint", this.context), "setLogLevel", - new Object[] { "com.example", "trace" }, - new String[] { String.class.getName(), String.class.getName() }); - assertThat(response).isNull(); - } - - @Test - public void loggerEndpointUnknownLogLevel() throws Exception { - MBeanExporter mbeanExporter = registerLoggersEndpoint(); - this.thrown.expect(MBeanException.class); - this.thrown.expectCause(hasMessage(containsString("No enum constant"))); - this.thrown.expectCause(hasMessage(containsString("LogLevel.INVALID"))); - mbeanExporter.getServer().invoke(getObjectName("loggersEndpoint", this.context), - "setLogLevel", new Object[] { "com.example", "invalid" }, - new String[] { String.class.getName(), String.class.getName() }); - } - - private MBeanExporter registerLoggersEndpoint() { - this.context = new GenericApplicationContext(); - this.context.registerBeanDefinition("endpointMbeanExporter", - new RootBeanDefinition(EndpointMBeanExporter.class)); - RootBeanDefinition bd = new RootBeanDefinition(LoggersEndpoint.class); - ConstructorArgumentValues values = new ConstructorArgumentValues(); - values.addIndexedArgumentValue(0, - new LogbackLoggingSystem(getClass().getClassLoader())); - bd.setConstructorArgumentValues(values); - this.context.registerBeanDefinition("loggersEndpoint", bd); - this.context.refresh(); - return this.context.getBean(EndpointMBeanExporter.class); - } - - private ObjectName getObjectName(String beanKey, GenericApplicationContext context) - throws MalformedObjectNameException { - return getObjectName("org.springframework.boot", beanKey, false, context); - } - - private ObjectName getObjectName(String domain, String beanKey, - boolean includeIdentity, ApplicationContext applicationContext) - throws MalformedObjectNameException { - if (includeIdentity) { - return ObjectNameManager.getInstance(String.format( - "%s:type=Endpoint,name=%s,identity=%s", domain, beanKey, ObjectUtils - .getIdentityHexString(applicationContext.getBean(beanKey)))); - } - return ObjectNameManager - .getInstance(String.format("%s:type=Endpoint,name=%s", domain, beanKey)); - } - - public static class TestEndpoint extends AbstractEndpoint { - - public TestEndpoint() { - super("test"); - } - - @Override - public String invoke() { - return "hello world"; - } - - } - - public static class TestEndpoint2 extends TestEndpoint { - - } - - public static class JsonMapConversionEndpoint - extends AbstractEndpoint> { - - public JsonMapConversionEndpoint() { - super("json_map_conversion"); - } - - @Override - public Map invoke() { - Map result = new LinkedHashMap<>(); - result.put("date", new Date()); - return result; - } - - } - - public static class JsonListConversionEndpoint - extends AbstractEndpoint> { - - public JsonListConversionEndpoint() { - super("json_list_conversion"); - } - - @Override - public List invoke() { - return Arrays.asList(new Date()); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMappingTests.java deleted file mode 100644 index 4f7a6e205c..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointHandlerMappingTests.java +++ /dev/null @@ -1,153 +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.Arrays; -import java.util.Collection; -import java.util.Collections; - -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.servlet.HandlerInterceptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link AbstractEndpointHandlerMapping}. - * - * @author Madhura Bhave - */ -public abstract class AbstractEndpointHandlerMappingTests { - - private final StaticApplicationContext context = new StaticApplicationContext(); - - @Test - public void securityInterceptorShouldBePresentForNonCorsRequest() throws Exception { - HandlerInterceptor securityInterceptor = mock(HandlerInterceptor.class); - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - AbstractEndpointHandlerMapping mapping = new TestEndpointHandlerMapping<>( - Collections.singletonList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.setSecurityInterceptor(securityInterceptor); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("POST", "/a")).getInterceptors()) - .contains(securityInterceptor); - } - - @Test - public void securityInterceptorIfNullShouldNotBeAdded() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - AbstractEndpointHandlerMapping mapping = new TestEndpointHandlerMapping<>( - Collections.singletonList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("POST", "/a")).getInterceptors()) - .hasSize(1); - } - - @Test - public void securityInterceptorShouldBePresentAfterCorsInterceptorForCorsRequest() - throws Exception { - HandlerInterceptor securityInterceptor = mock(HandlerInterceptor.class); - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - AbstractEndpointHandlerMapping mapping = new TestEndpointHandlerMapping<>( - Collections.singletonList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.setSecurityInterceptor(securityInterceptor); - mapping.afterPropertiesSet(); - MockHttpServletRequest request = request("POST", "/a"); - request.addHeader("Origin", "http://example.com"); - assertThat(mapping.getHandler(request).getInterceptors().length).isEqualTo(3); - assertThat(mapping.getHandler(request).getInterceptors()[2]) - .isEqualTo(securityInterceptor); - } - - @Test - public void pathNotMappedWhenGetPathReturnsNull() throws Exception { - TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - TestActionEndpoint other = new TestActionEndpoint(new TestEndpoint("b")); - AbstractEndpointHandlerMapping mapping = new TestEndpointHandlerMapping<>( - Arrays.asList(endpoint, other)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandlerMethods()).hasSize(1); - assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); - assertThat(mapping.getHandler(request("POST", "/b"))).isNotNull(); - } - - private MockHttpServletRequest request(String method, String requestURI) { - return new MockHttpServletRequest(method, requestURI); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - - private static class TestActionEndpoint extends EndpointMvcAdapter { - - TestActionEndpoint(TestEndpoint delegate) { - super(delegate); - } - - @Override - @PostMapping - public Object invoke() { - return null; - } - - } - - private static class TestEndpointHandlerMapping - extends AbstractEndpointHandlerMapping { - - TestEndpointHandlerMapping(Collection endpoints) { - super(endpoints); - } - - @Override - protected String getPath(MvcEndpoint endpoint) { - if (endpoint instanceof TestActionEndpoint) { - return super.getPath(endpoint); - } - return null; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpointTests.java deleted file mode 100644 index 1964174bef..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/AuditEventsMvcEndpointTests.java +++ /dev/null @@ -1,154 +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.time.Instant; -import java.util.Collections; -import java.util.Date; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.boot.actuate.audit.AuditEventRepository; -import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link AuditEventsMvcEndpoint}. - * - * @author Vedran Pavic - */ -@SpringBootTest -@RunWith(SpringRunner.class) -@TestPropertySource(properties = "management.security.enabled=false") -public class AuditEventsMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(AuditEventsMvcEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void contentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/auditevents").param("after", - "2016-11-01T10:00:00+0000")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void contentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform( - get("/application/auditevents").param("after", "2016-11-01T10:00:00+0000") - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()).andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void invokeWhenDisabledShouldReturnNotFoundStatus() throws Exception { - this.context.getBean(AuditEventsMvcEndpoint.class).setEnabled(false); - this.mvc.perform(get("/application/auditevents").param("after", - "2016-11-01T10:00:00+0000")).andExpect(status().isNotFound()); - } - - @Test - public void invokeFilterByDateAfter() throws Exception { - this.mvc.perform(get("/application/auditevents").param("after", - "2016-11-01T13:00:00+0000")).andExpect(status().isOk()) - .andExpect(content().string("{\"events\":[]}")); - } - - @Test - public void invokeFilterByPrincipalAndDateAfter() throws Exception { - this.mvc.perform(get("/application/auditevents").param("principal", "user") - .param("after", "2016-11-01T10:00:00+0000")) - .andExpect(status().isOk()) - .andExpect(content().string( - containsString("\"principal\":\"user\",\"type\":\"login\""))) - .andExpect(content().string(not(containsString("admin")))); - } - - @Test - public void invokeFilterByPrincipalAndDateAfterAndType() throws Exception { - this.mvc.perform(get("/application/auditevents").param("principal", "admin") - .param("after", "2016-11-01T10:00:00+0000").param("type", "logout")) - .andExpect(status().isOk()) - .andExpect(content().string( - containsString("\"principal\":\"admin\",\"type\":\"logout\""))) - .andExpect(content().string(not(containsString("login")))); - } - - @Test - public void invokeFilterWithoutDateAfterReturnBadRequestStatus() throws Exception { - this.mvc.perform(get("/application/auditevents")) - .andExpect(status().isBadRequest()); - } - - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - protected static class TestConfiguration { - - @Bean - public AuditEventRepository auditEventsRepository() { - AuditEventRepository repository = new InMemoryAuditEventRepository(3); - repository.add(createEvent("2016-11-01T11:00:00Z", "admin", "login")); - repository.add(createEvent("2016-11-01T12:00:00Z", "admin", "logout")); - repository.add(createEvent("2016-11-01T12:00:00Z", "user", "login")); - return repository; - } - - private AuditEvent createEvent(String instant, String principal, String type) { - return new AuditEvent(Date.from(Instant.parse(instant)), principal, type, - Collections.emptyMap()); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java deleted file mode 100644 index 74687fc841..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java +++ /dev/null @@ -1,185 +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.lang.reflect.Method; -import java.util.Arrays; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.method.HandlerMethod; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EndpointHandlerMapping}. - * - * @author Phillip Webb - * @author Dave Syer - */ -public class EndpointHandlerMappingTests extends AbstractEndpointHandlerMappingTests { - - private final StaticApplicationContext context = new StaticApplicationContext(); - - private Method method; - - @Before - public void init() throws Exception { - this.method = ReflectionUtils.findMethod(TestMvcEndpoint.class, "invoke"); - } - - @Test - public void withoutPrefix() throws Exception { - TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("a")); - TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("b")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpointA, endpointB)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("GET", "/a")).getHandler()) - .isEqualTo(new HandlerMethod(endpointA, this.method)); - assertThat(mapping.getHandler(request("GET", "/b")).getHandler()) - .isEqualTo(new HandlerMethod(endpointB, this.method)); - assertThat(mapping.getHandler(request("GET", "/c"))).isNull(); - assertThat((((HandlerMethod) mapping.getHandler(request("GET", "/")).getHandler()) - .getMethod().getName())).isEqualTo("links"); - } - - @Test - public void withPrefix() throws Exception { - TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("a")); - TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("b")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpointA, endpointB)); - mapping.setApplicationContext(this.context); - mapping.setPrefix("/a"); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/a")) - .getHandler()).isEqualTo(new HandlerMethod(endpointA, this.method)); - assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/b")) - .getHandler()).isEqualTo(new HandlerMethod(endpointB, this.method)); - assertThat( - (((HandlerMethod) mapping.getHandler(request("GET", "/a")).getHandler()) - .getMethod().getName())).isEqualTo("links"); - assertThat( - (((HandlerMethod) mapping.getHandler(request("GET", "/a/")).getHandler()) - .getMethod().getName())).isEqualTo("links"); - } - - @Test(expected = HttpRequestMethodNotSupportedException.class) - public void onlyGetHttpMethodForNonActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("GET", "/a"))).isNotNull(); - assertThat(mapping.getHandler(request("POST", "/a"))).isNull(); - } - - @Test - public void postHttpMethodForActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("POST", "/a"))).isNotNull(); - } - - @Test(expected = HttpRequestMethodNotSupportedException.class) - public void onlyPostHttpMethodForActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint)); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("POST", "/a"))).isNotNull(); - assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); - } - - @Test - public void disabled() throws Exception { - TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint)); - mapping.setDisabled(true); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); - } - - @Test - public void duplicatePath() throws Exception { - TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); - TestActionEndpoint other = new TestActionEndpoint(new TestEndpoint("a")); - EndpointHandlerMapping mapping = new EndpointHandlerMapping( - Arrays.asList(endpoint, other)); - mapping.setDisabled(true); - mapping.setApplicationContext(this.context); - mapping.afterPropertiesSet(); - assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); - assertThat(mapping.getHandler(request("POST", "/a"))).isNull(); - } - - private MockHttpServletRequest request(String method, String requestURI) { - return new MockHttpServletRequest(method, requestURI); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - - private static class TestActionEndpoint extends EndpointMvcAdapter { - - TestActionEndpoint(TestEndpoint delegate) { - super(delegate); - } - - @Override - @PostMapping - public Object invoke() { - return null; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java deleted file mode 100644 index 0daf978817..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java +++ /dev/null @@ -1,211 +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.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.MutablePropertySources; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link EnvironmentMvcEndpoint} - * - * @author Dave Syer - * @author Andy Wilkinson - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -@DirtiesContext -public class EnvironmentMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(EnvironmentEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - TestPropertyValues.of("foo:bar", "fool:baz") - .applyTo((ConfigurableApplicationContext) this.context); - } - - @Test - public void homeContentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/env")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void homeContentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/env").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void subContentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/env/foo")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void subContentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/env/foo").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void home() throws Exception { - this.mvc.perform(get("/application/env")).andExpect(status().isOk()) - .andExpect(content().string(containsString("systemProperties"))); - } - - @Test - public void sub() throws Exception { - this.mvc.perform(get("/application/env/foo")).andExpect(status().isOk()) - .andExpect(content().string("{\"foo\":\"bar\"}")); - } - - @Test - public void subWhenDisabled() throws Exception { - this.context.getBean(EnvironmentEndpoint.class).setEnabled(false); - this.mvc.perform(get("/application/env/foo")).andExpect(status().isNotFound()); - } - - @Test - public void regex() throws Exception { - Map map = new HashMap<>(); - map.put("food", null); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("null-value", map)); - this.mvc.perform(get("/application/env/foo.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"foo\":\"bar\""))) - .andExpect(content().string(containsString("\"fool\":\"baz\""))); - } - - @Test - public void nestedPathWhenPlaceholderCannotBeResolvedShouldReturnUnresolvedProperty() - throws Exception { - Map map = new HashMap<>(); - map.put("my.foo", "${my.bar}"); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("unresolved-placeholder", map)); - this.mvc.perform(get("/application/env/my.foo")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"my.foo\":\"${my.bar}\""))); - } - - @Test - public void nestedPathWithSensitivePlaceholderShouldSanitize() throws Exception { - Map map = new HashMap<>(); - map.put("my.foo", "${my.password}"); - map.put("my.password", "hello"); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("placeholder", map)); - this.mvc.perform(get("/application/env/my.foo")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"my.foo\":\"******\""))); - } - - @Test - public void nestedPathMatchedByRegexWhenPlaceholderCannotBeResolvedShouldReturnUnresolvedProperty() - throws Exception { - Map map = new HashMap<>(); - map.put("my.foo", "${my.bar}"); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("unresolved-placeholder", map)); - this.mvc.perform(get("/application/env/my.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"my.foo\":\"${my.bar}\""))); - } - - @Test - public void nestedPathMatchedByRegexWithSensitivePlaceholderShouldSanitize() - throws Exception { - Map map = new HashMap<>(); - map.put("my.foo", "${my.password}"); - map.put("my.password", "hello"); - ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources() - .addFirst(new MapPropertySource("placeholder", map)); - this.mvc.perform(get("/application/env/my.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"my.foo\":\"******\""))); - } - - @Test - public void propertyWithTypeOtherThanStringShouldNotFail() throws Exception { - MutablePropertySources propertySources = ((ConfigurableEnvironment) this.context - .getEnvironment()).getPropertySources(); - Map source = new HashMap<>(); - source.put("foo", Collections.singletonMap("bar", "baz")); - propertySources.addFirst(new MapPropertySource("test", source)); - this.mvc.perform(get("/application/env/foo.*")).andExpect(status().isOk()) - .andExpect(content().string("{\"foo\":{\"bar\":\"baz\"}}")); - } - - @Configuration - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class }) - public static class TestConfiguration { - - @Bean - public EnvironmentEndpoint endpoint() { - return new EnvironmentEndpoint(); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java deleted file mode 100644 index 998e5f7584..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java +++ /dev/null @@ -1,274 +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.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Status; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link HealthMvcEndpoint}. - * - * @author Christian Dupuis - * @author Dave Syer - * @author Andy Wilkinson - * @author EddĂș MelĂ©ndez - * @author Madhura Bhave - */ -public class HealthMvcEndpointTests { - - private static final List SECURITY_ROLES = new ArrayList<>( - Arrays.asList("HERO")); - - private HttpServletRequest request = new MockHttpServletRequest(); - - private HealthEndpoint endpoint = null; - - private HealthMvcEndpoint mvc = null; - - private HttpServletRequest defaultUser = createAuthenticationRequest("ROLE_ACTUATOR"); - - private HttpServletRequest hero = createAuthenticationRequest("HERO"); - - private HttpServletRequest createAuthenticationRequest(String role) { - MockServletContext servletContext = new MockServletContext(); - servletContext.declareRoles(role); - return new MockHttpServletRequest(servletContext); - } - - @Before - public void init() { - this.endpoint = mock(HealthEndpoint.class); - given(this.endpoint.isEnabled()).willReturn(true); - this.mvc = new HealthMvcEndpoint(this.endpoint); - } - - @Test - public void up() { - given(this.endpoint.invoke()).willReturn(new Health.Builder().up().build()); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - } - - @SuppressWarnings("unchecked") - @Test - public void down() { - given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof ResponseEntity).isTrue(); - ResponseEntity response = (ResponseEntity) result; - assertThat(response.getBody().getStatus() == Status.DOWN).isTrue(); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); - } - - @Test - @SuppressWarnings("unchecked") - public void customMapping() { - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().status("OK").build()); - this.mvc.setStatusMapping( - Collections.singletonMap("OK", HttpStatus.INTERNAL_SERVER_ERROR)); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof ResponseEntity).isTrue(); - ResponseEntity response = (ResponseEntity) result; - assertThat(response.getBody().getStatus().equals(new Status("OK"))).isTrue(); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - } - - @Test - @SuppressWarnings("unchecked") - public void customMappingWithRelaxedName() { - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().outOfService().build()); - this.mvc.setStatusMapping(Collections.singletonMap("out-OF-serVice", - HttpStatus.INTERNAL_SERVER_ERROR)); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof ResponseEntity).isTrue(); - ResponseEntity response = (ResponseEntity) result; - assertThat(response.getBody().getStatus().equals(Status.OUT_OF_SERVICE)).isTrue(); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - } - - @Test - public void presenceOfRightRoleShouldExposeDetails() { - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void managementSecurityDisabledShouldExposeDetails() throws Exception { - this.mvc = new HealthMvcEndpoint(this.endpoint, false); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void rightRoleNotPresentShouldNotExposeDetails() { - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.hero, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isNull(); - } - - @Test - public void rightAuthorityPresentShouldExposeDetails() throws Exception { - this.mvc = new HealthMvcEndpoint(this.endpoint, true, SECURITY_ROLES); - Authentication principal = mock(Authentication.class); - Set authorities = Collections - .singleton(new SimpleGrantedAuthority("HERO")); - doReturn(authorities).when(principal).getAuthorities(); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, principal); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void customRolePresentShouldExposeDetails() { - this.mvc = new HealthMvcEndpoint(this.endpoint, true, SECURITY_ROLES); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.hero, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void customRoleShouldNotExposeDetailsForDefaultRole() { - this.mvc = new HealthMvcEndpoint(this.endpoint, true, SECURITY_ROLES); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isNull(); - } - - @Test - public void customRoleFromListShouldExposeDetails() { - // gh-8314 - this.mvc = new HealthMvcEndpoint(this.endpoint, true, - Arrays.asList("HERO", "USER")); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.hero, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar"); - } - - @Test - public void customRoleFromListShouldNotExposeDetailsForDefaultRole() { - // gh-8314 - this.mvc = new HealthMvcEndpoint(this.endpoint, true, - Arrays.asList("HERO", "USER")); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - assertThat(((Health) result).getDetails().get("foo")).isNull(); - } - - @Test - public void healthIsCached() { - given(this.endpoint.getTimeToLive()).willReturn(10000L); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.defaultUser, null); - assertThat(result instanceof Health).isTrue(); - Health health = (Health) result; - assertThat(health.getStatus() == Status.UP).isTrue(); - assertThat(health.getDetails()).hasSize(1); - assertThat(health.getDetails().get("foo")).isEqualTo("bar"); - given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); - result = this.mvc.invoke(this.request, null); // insecure now - assertThat(result instanceof Health).isTrue(); - health = (Health) result; - // so the result is cached - assertThat(health.getStatus() == Status.UP).isTrue(); - // but the details are hidden - assertThat(health.getDetails()).isEmpty(); - } - - @Test - public void noCachingWhenTimeToLiveIsZero() { - given(this.endpoint.getTimeToLive()).willReturn(0L); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); - result = this.mvc.invoke(this.request, null); - @SuppressWarnings("unchecked") - Health health = ((ResponseEntity) result).getBody(); - assertThat(health.getStatus() == Status.DOWN).isTrue(); - } - - @Test - public void newValueIsReturnedOnceTtlExpires() throws InterruptedException { - given(this.endpoint.getTimeToLive()).willReturn(50L); - given(this.endpoint.invoke()) - .willReturn(new Health.Builder().up().withDetail("foo", "bar").build()); - Object result = this.mvc.invoke(this.request, null); - assertThat(result instanceof Health).isTrue(); - assertThat(((Health) result).getStatus() == Status.UP).isTrue(); - Thread.sleep(100); - given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build()); - result = this.mvc.invoke(this.request, null); - @SuppressWarnings("unchecked") - Health health = ((ResponseEntity) result).getBody(); - assertThat(health.getStatus() == Status.DOWN).isTrue(); - } -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java deleted file mode 100644 index 3744951a22..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java +++ /dev/null @@ -1,128 +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.io.IOException; -import java.util.concurrent.TimeUnit; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.util.FileCopyUtils; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link HeapdumpMvcEndpoint} OPTIONS call with security. - * - * @author Phillip Webb - */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class HeapdumpMvcEndpointSecureOptionsTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Autowired - private TestHeapdumpMvcEndpoint endpoint; - - @Before - public void setup() { - this.context.getBean(HeapdumpMvcEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @After - public void reset() { - this.endpoint.reset(); - } - - @Test - public void invokeOptionsShouldReturnSize() throws Exception { - this.mvc.perform(options("/application/heapdump")).andExpect(status().isOk()); - } - - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public HeapdumpMvcEndpoint endpoint() { - return new TestHeapdumpMvcEndpoint(); - } - - } - - private static class TestHeapdumpMvcEndpoint extends HeapdumpMvcEndpoint { - - private boolean available; - - private boolean locked; - - private String heapDump; - - TestHeapdumpMvcEndpoint() { - super(TimeUnit.SECONDS.toMillis(1)); - reset(); - } - - public void reset() { - this.available = true; - this.locked = false; - this.heapDump = "HEAPDUMP"; - } - - @Override - protected HeapDumper createHeapDumper() { - return (file, live) -> { - if (!TestHeapdumpMvcEndpoint.this.available) { - throw new HeapDumperUnavailableException("Not available", null); - } - if (TestHeapdumpMvcEndpoint.this.locked) { - throw new InterruptedException(); - } - if (file.exists()) { - throw new IOException("File exists"); - } - FileCopyUtils.copy(this.heapDump.getBytes(), file); - }; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java deleted file mode 100644 index 3f3c87bfb7..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java +++ /dev/null @@ -1,176 +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.io.IOException; -import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; - -import org.fusesource.hawtbuf.ByteArrayInputStream; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.util.FileCopyUtils; -import org.springframework.web.context.WebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link HeapdumpMvcEndpoint}. - * - * @author Phillip Webb - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -public class HeapdumpMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Autowired - private TestHeapdumpMvcEndpoint endpoint; - - @Before - public void setup() { - this.context.getBean(HeapdumpMvcEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @After - public void reset() { - this.endpoint.reset(); - } - - @Test - public void invokeWhenDisabledShouldReturnNotFoundStatus() throws Exception { - this.endpoint.setEnabled(false); - this.mvc.perform(get("/application/heapdump")).andExpect(status().isNotFound()); - } - - @Test - public void invokeWhenNotAvailableShouldReturnServiceUnavailableStatus() - throws Exception { - this.endpoint.setAvailable(false); - this.mvc.perform(get("/application/heapdump")) - .andExpect(status().isServiceUnavailable()); - } - - @Test - public void invokeWhenLockedShouldReturnTooManyRequestsStatus() throws Exception { - this.endpoint.setLocked(true); - this.mvc.perform(get("/application/heapdump")) - .andExpect(status().isTooManyRequests()); - assertThat(Thread.interrupted()).isTrue(); - } - - @Test - public void invokeShouldReturnGzipContent() throws Exception { - MvcResult result = this.mvc.perform(get("/application/heapdump")) - .andExpect(status().isOk()).andReturn(); - byte[] bytes = result.getResponse().getContentAsByteArray(); - GZIPInputStream stream = new GZIPInputStream(new ByteArrayInputStream(bytes)); - byte[] uncompressed = FileCopyUtils.copyToByteArray(stream); - assertThat(uncompressed).isEqualTo("HEAPDUMP".getBytes()); - } - - @Test - public void invokeOptionsShouldReturnSize() throws Exception { - this.mvc.perform(options("/application/heapdump")).andExpect(status().isOk()); - } - - @Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public HeapdumpMvcEndpoint endpoint() { - return new TestHeapdumpMvcEndpoint(); - } - - } - - private static class TestHeapdumpMvcEndpoint extends HeapdumpMvcEndpoint { - - private boolean available; - - private boolean locked; - - private String heapDump; - - TestHeapdumpMvcEndpoint() { - super(TimeUnit.SECONDS.toMillis(1)); - reset(); - } - - public void reset() { - this.available = true; - this.locked = false; - this.heapDump = "HEAPDUMP"; - } - - @Override - protected HeapDumper createHeapDumper() { - return (file, live) -> { - if (!TestHeapdumpMvcEndpoint.this.available) { - throw new HeapDumperUnavailableException("Not available", null); - } - if (TestHeapdumpMvcEndpoint.this.locked) { - throw new InterruptedException(); - } - if (file.exists()) { - throw new IOException("File exists"); - } - FileCopyUtils.copy(this.heapDump.getBytes(), file); - }; - } - - public void setAvailable(boolean available) { - this.available = available; - } - - public void setLocked(boolean locked) { - this.locked = locked; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java deleted file mode 100644 index 96bb0a523d..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java +++ /dev/null @@ -1,133 +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.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.InfoEndpoint; -import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link InfoEndpoint} with {@link MockMvc}. - * - * @author Meang Akira Tanaka - * @author Stephane Nicoll - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = { "info.app.name=MyService", - "management.security.enabled=false" }) -public class InfoMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(InfoEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void home() throws Exception { - this.mvc.perform(get("/application/info")).andExpect(status().isOk()) - .andExpect(content().string(containsString( - "\"beanName1\":{\"key11\":\"value11\",\"key12\":\"value12\"}"))) - .andExpect(content().string(containsString( - "\"beanName2\":{\"key21\":\"value21\",\"key22\":\"value22\"}"))); - } - - @Test - public void contentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/info")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void contentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/info").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public InfoEndpoint endpoint() { - return new InfoEndpoint(Arrays.asList(beanName1(), beanName2())); - } - - @Bean - public InfoContributor beanName1() { - return (builder) -> { - Map content = new LinkedHashMap<>(); - content.put("key11", "value11"); - content.put("key12", "value12"); - builder.withDetail("beanName1", content); - }; - } - - @Bean - public InfoContributor beanName2() { - return (builder) -> { - Map content = new LinkedHashMap<>(); - content.put("key21", "value21"); - content.put("key22", "value22"); - builder.withDetail("beanName2", content); - }; - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithNoInfoContributorsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithNoInfoContributorsTests.java deleted file mode 100644 index f13413ed0b..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithNoInfoContributorsTests.java +++ /dev/null @@ -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.mvc; - -import java.util.Collections; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.InfoEndpoint; -import org.springframework.boot.actuate.info.InfoContributor; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link InfoEndpoint} with {@link MockMvc} when there are no - * {@link InfoContributor InfoContributors}. - * - * @author Meang Akira Tanaka - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -public class InfoMvcEndpointWithNoInfoContributorsTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(InfoEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void home() throws Exception { - this.mvc.perform(get("/application/info")).andExpect(status().isOk()); - } - - @Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public InfoEndpoint endpoint() { - return new InfoEndpoint(Collections.emptyList()); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpointTests.java deleted file mode 100644 index 772b1ba7bc..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpointTests.java +++ /dev/null @@ -1,124 +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 org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.mock.env.MockEnvironment; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link LogFileMvcEndpoint}. - * - * @author Johannes Edmeier - * @author Phillip Webb - */ -public class LogFileMvcEndpointTests { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - private LogFileMvcEndpoint mvc; - - private MockEnvironment environment; - - private File logFile; - - @Before - public void before() throws IOException { - this.logFile = this.temp.newFile(); - FileCopyUtils.copy("--TEST--".getBytes(), this.logFile); - this.environment = new MockEnvironment(); - this.mvc = new LogFileMvcEndpoint(); - this.mvc.setEnvironment(this.environment); - } - - @Test - public void notAvailableWithoutLogFile() throws Exception { - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest( - HttpMethod.HEAD.name(), "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); - } - - @Test - public void notAvailableWithMissingLogFile() throws Exception { - this.environment.setProperty("logging.file", "no_test.log"); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest( - HttpMethod.HEAD.name(), "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); - } - - @Test - public void availableWithLogFile() throws Exception { - this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest( - HttpMethod.HEAD.name(), "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - } - - @Test - public void notAvailableIfDisabled() throws Exception { - this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); - this.mvc.setEnabled(false); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest( - HttpMethod.HEAD.name(), "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); - } - - @Test - public void invokeGetsContent() throws Exception { - this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), - "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getContentAsString()).isEqualTo("--TEST--"); - } - - @Test - public void invokeGetsContentExternalFile() throws Exception { - this.mvc.setExternalFile(this.logFile); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), - "/logfile"); - this.mvc.invoke(request, response); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat("--TEST--").isEqualTo(response.getContentAsString()); - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java deleted file mode 100644 index d28a5adc50..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/LoggersMvcEndpointTests.java +++ /dev/null @@ -1,228 +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.EnumSet; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.LoggersEndpoint; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.logging.LogLevel; -import org.springframework.boot.logging.LoggerConfiguration; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link LoggersMvcEndpoint}. - * - * @author Ben Hale - * @author Phillip Webb - * @author EddĂș MelĂ©ndez - * @author Stephane Nicoll - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -public class LoggersMvcEndpointTests { - - private static final String PATH = "/application/loggers"; - - @Autowired - private WebApplicationContext context; - - @Autowired - private LoggingSystem loggingSystem; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(LoggersEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context) - .alwaysDo(MockMvcResultHandlers.print()).build(); - } - - @Before - @After - public void resetMocks() { - Mockito.reset(this.loggingSystem); - given(this.loggingSystem.getSupportedLogLevels()) - .willReturn(EnumSet.allOf(LogLevel.class)); - } - - @Test - public void getLoggerShouldReturnAllLoggerConfigurations() throws Exception { - given(this.loggingSystem.getLoggerConfigurations()).willReturn(Collections - .singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); - String expected = "{\"levels\":[\"OFF\",\"FATAL\",\"ERROR\",\"WARN\",\"INFO\",\"DEBUG\",\"TRACE\"]," - + "\"loggers\":{\"ROOT\":{\"configuredLevel\":null,\"effectiveLevel\":\"DEBUG\"}}}"; - this.mvc.perform(get(PATH + "")).andExpect(status().isOk()) - .andExpect(content().json(expected)); - } - - @Test - public void getLoggersWhenDisabledShouldReturnNotFound() throws Exception { - this.context.getBean(LoggersEndpoint.class).setEnabled(false); - this.mvc.perform(get(PATH + "")).andExpect(status().isNotFound()); - } - - @Test - public void getLoggerShouldReturnLogLevels() throws Exception { - given(this.loggingSystem.getLoggerConfiguration("ROOT")) - .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); - this.mvc.perform(get(PATH + "/ROOT")).andExpect(status().isOk()) - .andExpect(content().string(equalTo( - "{\"configuredLevel\":null,\"effectiveLevel\":\"DEBUG\"}"))); - } - - @Test - public void getLoggersRootWhenDisabledShouldReturnNotFound() throws Exception { - this.context.getBean(LoggersEndpoint.class).setEnabled(false); - this.mvc.perform(get(PATH + "/ROOT")).andExpect(status().isNotFound()); - } - - @Test - public void getLoggersWhenLoggerNotFoundShouldReturnNotFound() throws Exception { - this.mvc.perform(get(PATH + "/com.does.not.exist")) - .andExpect(status().isNotFound()); - } - - @Test - public void contentTypeForGetDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get(PATH + "")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void contentTypeForGetCanBeApplicationJson() throws Exception { - this.mvc.perform(get(PATH + "").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void setLoggerUsingApplicationJsonShouldSetLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\":\"debug\"}")) - .andExpect(status().isNoContent()); - verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); - } - - @Test - public void setLoggerUsingActuatorV2JsonShouldSetLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT") - .contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON) - .content("{\"configuredLevel\":\"debug\"}")) - .andExpect(status().isNoContent()); - verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); - } - - @Test - public void setLoggerWhenDisabledShouldReturnNotFound() throws Exception { - this.context.getBean(LoggersEndpoint.class).setEnabled(false); - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\":\"DEBUG\"}")) - .andExpect(status().isNotFound()); - verifyZeroInteractions(this.loggingSystem); - } - - @Test - public void setLoggerWithWrongLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\":\"other\"}")) - .andExpect(status().is4xxClientError()); - verifyZeroInteractions(this.loggingSystem); - } - - @Test - public void setLoggerWithNullLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{\"configuredLevel\": null}")) - .andExpect(status().isNoContent()); - verify(this.loggingSystem).setLogLevel("ROOT", null); - } - - @Test - public void setLoggerWithNoLogLevel() throws Exception { - this.mvc.perform(post(PATH + "/ROOT").contentType(MediaType.APPLICATION_JSON) - .content("{}")).andExpect(status().isNoContent()); - verify(this.loggingSystem).setLogLevel("ROOT", null); - } - - @Test - public void logLevelForLoggerWithNameThatCouldBeMistakenForAPathExtension() - throws Exception { - given(this.loggingSystem.getLoggerConfiguration("com.png")) - .willReturn(new LoggerConfiguration("com.png", null, LogLevel.DEBUG)); - this.mvc.perform(get(PATH + "/com.png")).andExpect(status().isOk()) - .andExpect(content().string(equalTo( - "{\"configuredLevel\":null,\"effectiveLevel\":\"DEBUG\"}"))); - } - - @Configuration - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - public static class TestConfiguration { - - @Bean - public LoggingSystem loggingSystem() { - return mock(LoggingSystem.class); - } - - @Bean - public LoggersEndpoint endpoint(LoggingSystem loggingSystem) { - return new LoggersEndpoint(loggingSystem); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java deleted file mode 100644 index 206a896790..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpointTests.java +++ /dev/null @@ -1,198 +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.ArrayList; -import java.util.Collections; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.MetricsEndpoint; -import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link MetricsMvcEndpoint} - * - * @author Andy Wilkinson - * @author Sergei Egorov - */ -@RunWith(SpringRunner.class) -@DirtiesContext -@SpringBootTest -@TestPropertySource(properties = "management.security.enabled=false") -public class MetricsMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.context.getBean(MetricsEndpoint.class).setEnabled(true); - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void home() throws Exception { - this.mvc.perform(get("/application/metrics")).andExpect(status().isOk()) - .andExpect(content().string(containsString("\"foo\":1"))); - } - - @Test - public void homeContentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/metrics")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void homeContentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/metrics").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void specificMetricContentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(get("/application/metrics/foo")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - } - - @Test - public void specificMetricContentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(get("/application/metrics/foo").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - } - - @Test - public void homeWhenDisabled() throws Exception { - this.context.getBean(MetricsEndpoint.class).setEnabled(false); - this.mvc.perform(get("/application/metrics")).andExpect(status().isNotFound()); - } - - @Test - public void specificMetric() throws Exception { - this.mvc.perform(get("/application/metrics/foo")).andExpect(status().isOk()) - .andExpect(content().string(equalTo("{\"foo\":1}"))); - } - - @Test - public void specificMetricWithNameThatCouldBeMistakenForAPathExtension() - throws Exception { - this.mvc.perform(get("/application/metrics/bar.png")).andExpect(status().isOk()) - .andExpect(content().string(equalTo("{\"bar.png\":1}"))); - } - - @Test - public void specificMetricWhenDisabled() throws Exception { - this.context.getBean(MetricsEndpoint.class).setEnabled(false); - this.mvc.perform(get("/application/metrics/foo")) - .andExpect(status().isNotFound()); - } - - @Test - public void specificMetricThatDoesNotExist() throws Exception { - this.mvc.perform(get("/application/metrics/bar")) - .andExpect(status().isNotFound()); - } - - @Test - public void regexAll() throws Exception { - String expected = "\"foo\":1,\"bar.png\":1,\"group1.a\":1,\"group1.b\":1," - + "\"group2.a\":1,\"group2_a\":1"; - this.mvc.perform(get("/application/metrics/.*")).andExpect(status().isOk()) - .andExpect(content().string(containsString(expected))); - } - - @Test - public void regexGroupDot() throws Exception { - String expected = "\"group1.a\":1,\"group1.b\":1,\"group2.a\":1"; - this.mvc.perform(get("/application/metrics/group[0-9]+\\..*")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString(expected))); - } - - @Test - public void regexGroup1() throws Exception { - String expected = "\"group1.a\":1,\"group1.b\":1"; - this.mvc.perform(get("/application/metrics/group1\\..*")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString(expected))); - } - - @Test - public void specificMetricWithDot() throws Exception { - this.mvc.perform(get("/application/metrics/group2.a")).andExpect(status().isOk()) - .andExpect(content().string(containsString("1"))); - } - - @Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - @Configuration - public static class TestConfiguration { - - @Bean - public MetricsEndpoint endpoint() { - return new MetricsEndpoint(() -> { - ArrayList> metrics = new ArrayList<>(); - metrics.add(new Metric<>("foo", 1)); - metrics.add(new Metric<>("bar.png", 1)); - metrics.add(new Metric<>("group1.a", 1)); - metrics.add(new Metric<>("group1.b", 1)); - metrics.add(new Metric<>("group2.a", 1)); - metrics.add(new Metric<>("group2_a", 1)); - metrics.add(new Metric("baz", null)); - return Collections.unmodifiableList(metrics); - }); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java index ae4072bf2c..f0c197d87f 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointCorsIntegrationTests.java @@ -19,15 +19,20 @@ package org.springframework.boot.actuate.endpoint.mvc; import org.junit.Before; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; +import org.springframework.boot.actuate.endpoint.BeansEndpoint; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockServletContext; import org.springframework.test.web.servlet.MockMvc; @@ -54,18 +59,19 @@ public class MvcEndpointCorsIntegrationTests { this.context.setServletContext(new MockServletContext()); this.context.register(JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class, - JolokiaManagementContextConfiguration.class, - WebMvcAutoConfiguration.class); + WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + EndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class); } @Test public void corsIsDisabledByDefault() throws Exception { - createMockMvc() - .perform(options("/application/beans").header("Origin", "foo.example.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) - .andExpect( + MockMvc mockMvc = createMockMvc(); + System.out.println(new ConditionEvaluationReportMessage( + this.context.getBean(ConditionEvaluationReport.class))); + mockMvc.perform(options("/application/beans").header("Origin", "foo.example.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect( header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @@ -137,7 +143,7 @@ public class MvcEndpointCorsIntegrationTests { TestPropertyValues.of("endpoints.cors.allowed-origins:foo.example.com", "endpoints.cors.allowed-methods:GET,HEAD").applyTo(this.context); createMockMvc() - .perform(options("/application/health") + .perform(options("/application/beans") .header(HttpHeaders.ORIGIN, "foo.example.com") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")) .andExpect(status().isOk()).andExpect(header() @@ -178,4 +184,14 @@ public class MvcEndpointCorsIntegrationTests { .andExpect(status().isOk()); } + @Configuration + static class EndpointConfiguration { + + @Bean + public BeansEndpoint beansEndpoint() { + return new BeansEndpoint(); + } + + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointIntegrationTests.java index 324ca1b908..4e4aad1fe0 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointIntegrationTests.java @@ -19,9 +19,11 @@ package org.springframework.boot.actuate.endpoint.mvc; import org.junit.After; import org.junit.Test; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -30,6 +32,7 @@ import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguratio import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.annotation.Import; @@ -42,10 +45,8 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import static org.hamcrest.Matchers.startsWith; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -55,8 +56,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. */ public class MvcEndpointIntegrationTests { - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); - private AnnotationConfigWebApplicationContext context; @After @@ -65,52 +64,6 @@ public class MvcEndpointIntegrationTests { this.context.close(); } - @Test - public void defaultJsonResponseIsNotIndented() throws Exception { - TestSecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(SecureConfiguration.class); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/application/mappings")) - .andExpect(content().string(startsWith("{\""))); - } - - @Test - public void jsonResponsesCanBeIndented() throws Exception { - assertIndentedJsonResponse(SecureConfiguration.class); - } - - @Test - public void jsonResponsesCanBeIndentedWhenSpringHateoasIsAutoConfigured() - throws Exception { - assertIndentedJsonResponse(SpringHateoasConfiguration.class); - } - - @Test - public void jsonResponsesCanBeIndentedWhenSpringDataRestIsAutoConfigured() - throws Exception { - assertIndentedJsonResponse(SpringDataRestConfiguration.class); - } - - @Test - public void fileExtensionNotFound() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(DefaultConfiguration.class); - MockMvc mockMvc = createMockMvc(); - mockMvc.perform(get("/application/beans.cmd")).andExpect(status().isNotFound()); - } - - @Test - public void jsonExtensionProvided() throws Exception { - TestSecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(SecureConfiguration.class); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/application/beans.json")).andExpect(status().isOk()); - } - @Test public void endpointsAreSecureByDefault() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); @@ -129,19 +82,6 @@ public class MvcEndpointIntegrationTests { mockMvc.perform(get("/management/beans")).andExpect(status().isUnauthorized()); } - @Test - public void endpointsAreSecureWithNonActuatorRoleWithCustomContextPath() - throws Exception { - TestSecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "N/A", "ROLE_USER")); - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(SecureConfiguration.class); - TestPropertyValues.of("management.context-path:/management") - .applyTo(this.context); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/management/beans")).andExpect(status().isForbidden()); - } - @Test public void endpointsAreSecureWithActuatorRoleWithCustomContextPath() throws Exception { @@ -174,22 +114,6 @@ public class MvcEndpointIntegrationTests { mockMvc.perform(get("/application/beans")).andExpect(status().isOk()); } - private void assertIndentedJsonResponse(Class configuration) throws Exception { - TestSecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(configuration); - TestPropertyValues.of("spring.jackson.serialization.indent-output:true") - .applyTo(this.context); - MockMvc mockMvc = createSecureMockMvc(); - mockMvc.perform(get("/application/mappings")) - .andExpect(content().string(startsWith("{" + LINE_SEPARATOR))); - } - - private MockMvc createMockMvc() { - return doCreateMockMvc(); - } - private MockMvc createSecureMockMvc() { return doCreateMockMvc(springSecurity()); } @@ -205,10 +129,12 @@ public class MvcEndpointIntegrationTests { } @ImportAutoConfiguration({ JacksonAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, AuditAutoConfiguration.class, + ServletEndpointAutoConfiguration.class, AuditAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class, - AuditAutoConfiguration.class }) + ManagementContextAutoConfiguration.class, AuditAutoConfiguration.class, + DispatcherServletAutoConfiguration.class }) static class DefaultConfiguration { } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java deleted file mode 100644 index aa2cb7274e..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java +++ /dev/null @@ -1,171 +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.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.servlet.http.HttpServletResponse; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.test.rule.OutputCapture; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.method.HandlerMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link MvcEndpointSecurityInterceptor}. - * - * @author Madhura Bhave - */ -public class MvcEndpointSecurityInterceptorTests { - - @Rule - public OutputCapture output = new OutputCapture(); - - private MvcEndpointSecurityInterceptor securityInterceptor; - - private TestMvcEndpoint mvcEndpoint; - - private TestEndpoint endpoint; - - private HandlerMethod handlerMethod; - - private MockHttpServletRequest request; - - private HttpServletResponse response; - - private MockServletContext servletContext; - - private List roles; - - @Before - public void setup() throws Exception { - this.roles = Arrays.asList("SUPER_HERO"); - this.securityInterceptor = new MvcEndpointSecurityInterceptor(true, this.roles); - this.endpoint = new TestEndpoint("a"); - this.mvcEndpoint = new TestMvcEndpoint(this.endpoint); - this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke"); - this.servletContext = new MockServletContext(); - this.request = new MockHttpServletRequest(this.servletContext); - this.response = mock(HttpServletResponse.class); - } - - @Test - public void securityDisabledShouldAllowAccess() throws Exception { - this.securityInterceptor = new MvcEndpointSecurityInterceptor(false, this.roles); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isTrue(); - } - - @Test - public void endpointIfRoleIsPresentShouldAllowAccess() throws Exception { - this.servletContext.declareRoles("SUPER_HERO"); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isTrue(); - } - - @Test - public void endpointIfNotAuthenticatedShouldNotAllowAccess() throws Exception { - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - verify(this.response).sendError(HttpStatus.UNAUTHORIZED.value(), - "Full authentication is required to access this resource."); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - assertThat(this.output.toString()) - .containsOnlyOnce("Full authentication is required to access actuator " - + "endpoints. Consider adding Spring Security or set " - + "'management.security.enabled' to false"); - } - - @Test - public void endpointIfRoleIsNotCorrectShouldNotAllowAccess() throws Exception { - Principal principal = mock(Principal.class); - this.request.setUserPrincipal(principal); - this.servletContext.declareRoles("HERO"); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - verify(this.response).sendError(HttpStatus.FORBIDDEN.value(), - "Access is denied. User must have one of the these roles: SUPER_HERO"); - } - - @Test - public void endpointIfRoleNotCorrectShouldCheckAuthorities() throws Exception { - Principal principal = mock(Principal.class); - this.request.setUserPrincipal(principal); - Authentication authentication = mock(Authentication.class); - Set authorities = Collections - .singleton(new SimpleGrantedAuthority("SUPER_HERO")); - doReturn(authorities).when(authentication).getAuthorities(); - SecurityContextHolder.getContext().setAuthentication(authentication); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isTrue(); - } - - @Test - public void endpointIfRoleAndAuthoritiesNotCorrectShouldNotAllowAccess() - throws Exception { - Principal principal = mock(Principal.class); - this.request.setUserPrincipal(principal); - Authentication authentication = mock(Authentication.class); - Set authorities = Collections - .singleton(new SimpleGrantedAuthority("HERO")); - doReturn(authorities).when(authentication).getAuthorities(); - SecurityContextHolder.getContext().setAuthentication(authentication); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointsTests.java deleted file mode 100644 index 3e3b6a984f..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointsTests.java +++ /dev/null @@ -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.endpoint.mvc; - -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.support.StaticApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link MvcEndpoints}. - * - * @author Dave Syer - */ -public class MvcEndpointsTests { - - private MvcEndpoints endpoints = new MvcEndpoints(); - - private StaticApplicationContext context = new StaticApplicationContext(); - - @Test - public void picksUpEndpointDelegates() throws Exception { - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint", - new TestEndpoint()); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints()).hasSize(1); - } - - @Test - public void picksUpEndpointDelegatesFromParent() throws Exception { - StaticApplicationContext parent = new StaticApplicationContext(); - this.context.setParent(parent); - parent.getDefaultListableBeanFactory().registerSingleton("endpoint", - new TestEndpoint()); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints()).hasSize(1); - } - - @Test - public void picksUpMvcEndpoints() throws Exception { - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint", - new EndpointMvcAdapter(new TestEndpoint())); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints()).hasSize(1); - } - - @Test - public void changesPath() throws Exception { - TestPropertyValues.of("endpoints.test.path=/foo/bar/").applyTo(this.context); - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint", - new TestEndpoint()); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints()).hasSize(1); - assertThat(this.endpoints.getEndpoints().iterator().next().getPath()) - .isEqualTo("/foo/bar"); - } - - @Test - public void getEndpointsForSpecifiedType() throws Exception { - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint-1", - new TestMvcEndpoint(new TestEndpoint())); - this.context.getDefaultListableBeanFactory().registerSingleton("endpoint-2", - new OtherTestMvcEndpoint(new TestEndpoint())); - this.endpoints.setApplicationContext(this.context); - this.endpoints.afterPropertiesSet(); - assertThat(this.endpoints.getEndpoints(TestMvcEndpoint.class)).hasSize(1); - } - - @ConfigurationProperties("endpoints.test") - protected static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint() { - super("test"); - } - - @Override - public String invoke() { - return "foo"; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(Endpoint delegate) { - super(delegate); - } - - } - - private static class OtherTestMvcEndpoint extends EndpointMvcAdapter { - - OtherTestMvcEndpoint(Endpoint delegate) { - super(delegate); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java index 560c664739..4329b23ece 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityHealthMvcEndpointIntegrationTests.java @@ -16,18 +16,24 @@ package org.springframework.boot.actuate.endpoint.mvc; +import java.util.Map; + import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions; @@ -75,9 +81,11 @@ public class NoSpringSecurityHealthMvcEndpointIntegrationTests { } @ImportAutoConfiguration({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class }) + HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class }) @Configuration static class TestConfiguration { @@ -86,6 +94,18 @@ public class NoSpringSecurityHealthMvcEndpointIntegrationTests { return () -> Health.up().withDetail("hello", "world").build(); } + @Bean + public HealthEndpoint healthEndpoint( + Map healthIndicators) { + return new HealthEndpoint(new OrderedHealthAggregator(), healthIndicators); + } + + @Bean + public HealthWebEndpointExtension healthWebEndpointExtension( + HealthEndpoint delegate) { + return new HealthWebEndpointExtension(delegate); + } + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java deleted file mode 100644 index b09383dada..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java +++ /dev/null @@ -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.security.Principal; -import java.util.Arrays; -import java.util.List; - -import javax.servlet.http.HttpServletResponse; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions; -import org.springframework.boot.testsupport.runner.classpath.ModifiedClassPathRunner; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.web.method.HandlerMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link MvcEndpointSecurityInterceptor} when Spring Security is not available. - * - * @author Madhura Bhave - */ -@RunWith(ModifiedClassPathRunner.class) -@ClassPathExclusions("spring-security-*.jar") -public class NoSpringSecurityMvcEndpointSecurityInterceptorTests { - - private MvcEndpointSecurityInterceptor securityInterceptor; - - private TestMvcEndpoint mvcEndpoint; - - private TestEndpoint endpoint; - - private HandlerMethod handlerMethod; - - private MockHttpServletRequest request; - - private HttpServletResponse response; - - private MockServletContext servletContext; - - private List roles; - - @Before - public void setup() throws Exception { - this.roles = Arrays.asList("SUPER_HERO"); - this.securityInterceptor = new MvcEndpointSecurityInterceptor(true, this.roles); - this.endpoint = new TestEndpoint("a"); - this.mvcEndpoint = new TestMvcEndpoint(this.endpoint); - this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke"); - this.servletContext = new MockServletContext(); - this.request = new MockHttpServletRequest(this.servletContext); - this.response = mock(HttpServletResponse.class); - } - - @Test - public void endpointIfRoleNotPresentShouldNotValidateAuthorities() throws Exception { - Principal principal = mock(Principal.class); - this.request.setUserPrincipal(principal); - this.servletContext.declareRoles("HERO"); - assertThat(this.securityInterceptor.preHandle(this.request, this.response, - this.handlerMethod)).isFalse(); - } - - private static class TestEndpoint extends AbstractEndpoint { - - TestEndpoint(String id) { - super(id); - } - - @Override - public Object invoke() { - return null; - } - - } - - private static class TestMvcEndpoint extends EndpointMvcAdapter { - - TestMvcEndpoint(TestEndpoint delegate) { - super(delegate); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpointTests.java deleted file mode 100644 index 88ae84a49c..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpointTests.java +++ /dev/null @@ -1,133 +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.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link ShutdownMvcEndpoint}. - * - * @author Dave Syer - * - */ -@SpringBootTest(properties = { "management.security.enabled=false", - "endpoints.shutdown.enabled=true" }) -@RunWith(SpringRunner.class) -public class ShutdownMvcEndpointTests { - - @Autowired - private WebApplicationContext context; - - private MockMvc mvc; - - @Before - public void setUp() { - this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); - } - - @Test - public void contentTypeDefaultsToActuatorV2Json() throws Exception { - this.mvc.perform(post("/application/shutdown")).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8")); - assertThat(this.context.getBean(CountDownLatch.class).await(30, TimeUnit.SECONDS)) - .isTrue(); - } - - @Test - public void contentTypeCanBeApplicationJson() throws Exception { - this.mvc.perform(post("/application/shutdown").header(HttpHeaders.ACCEPT, - MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()) - .andExpect(header().string("Content-Type", - MediaType.APPLICATION_JSON_UTF8_VALUE)); - assertThat(this.context.getBean(CountDownLatch.class).await(30, TimeUnit.SECONDS)) - .isTrue(); - } - - @Configuration - @Import({ JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class }) - public static class TestConfiguration { - - @Bean - public TestShutdownEndpoint endpoint() { - return new TestShutdownEndpoint(contextCloseLatch()); - } - - @Bean - public CountDownLatch contextCloseLatch() { - return new CountDownLatch(1); - } - - } - - private static class TestShutdownEndpoint extends ShutdownEndpoint { - - private final CountDownLatch contextCloseLatch; - - TestShutdownEndpoint(CountDownLatch contextCloseLatch) { - this.contextCloseLatch = contextCloseLatch; - } - - @Override - public void setApplicationContext(ApplicationContext context) - throws BeansException { - ConfigurableApplicationContext mockContext = mock( - ConfigurableApplicationContext.class); - willAnswer((invocation) -> { - TestShutdownEndpoint.this.contextCloseLatch.countDown(); - return null; - }).given(mockContext).close(); - super.setApplicationContext(mockContext); - } - - } - -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..7505f8d6c2 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsEndpointWebIntegrationTests.java @@ -0,0 +1,110 @@ +/* + * 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.time.Instant; +import java.util.Collections; +import java.util.Date; + +import net.minidev.json.JSONArray; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link AuditEventsEndpoint} and + * {@link AuditEventsWebEndpointExtension} exposed by Jersey, Spring MVC, and WebFlux. + * + * @author Vedran Pavic + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class AuditEventsEndpointWebIntegrationTests { + + private static WebTestClient client; + + @Test + public void eventsWithDateAfter() throws Exception { + client.get() + .uri((builder) -> builder.path("/application/auditevents") + .queryParam("after", "2016-11-01T13:00:00%2B00:00").build()) + .exchange().expectStatus().isOk().expectBody().jsonPath("events") + .isEmpty(); + } + + @Test + public void eventsWithPrincipalAndDateAfter() throws Exception { + client.get() + .uri((builder) -> builder.path("/application/auditevents") + .queryParam("after", "2016-11-01T10:00:00%2B00:00") + .queryParam("principal", "user").build()) + .exchange().expectStatus().isOk().expectBody() + .jsonPath("events.[*].principal") + .isEqualTo(new JSONArray().appendElement("user")); + } + + @Test + public void eventsWithPrincipalDateAfterAndType() throws Exception { + client.get() + .uri((builder) -> builder.path("/application/auditevents") + .queryParam("after", "2016-11-01T10:00:00%2B00:00") + .queryParam("principal", "admin").queryParam("type", "logout") + .build()) + .exchange().expectStatus().isOk().expectBody() + .jsonPath("events.[*].principal") + .isEqualTo(new JSONArray().appendElement("admin")) + .jsonPath("events.[*].type") + .isEqualTo(new JSONArray().appendElement("logout")); + } + + @Configuration + protected static class TestConfiguration { + + @Bean + public AuditEventRepository auditEventsRepository() { + AuditEventRepository repository = new InMemoryAuditEventRepository(3); + repository.add(createEvent("2016-11-01T11:00:00Z", "admin", "login")); + repository.add(createEvent("2016-11-01T12:00:00Z", "admin", "logout")); + repository.add(createEvent("2016-11-01T12:00:00Z", "user", "login")); + return repository; + } + + @Bean + public AuditEventsEndpoint auditEventsEndpoint() { + return new AuditEventsEndpoint(auditEventsRepository()); + } + + @Bean + public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension() { + return new AuditEventsWebEndpointExtension(auditEventsEndpoint()); + } + + private AuditEvent createEvent(String instant, String principal, String type) { + return new AuditEvent(Date.from(Instant.parse(instant)), principal, type, + Collections.emptyMap()); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtensionTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtensionTests.java new file mode 100644 index 0000000000..763cb842fd --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AuditEventsWebEndpointExtensionTests.java @@ -0,0 +1,60 @@ +/* + * 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.junit.Test; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AuditEventsWebEndpointExtension}. + * + * @author Andy Wilkinson + */ +public class AuditEventsWebEndpointExtensionTests { + + private final AuditEventRepository repository = mock(AuditEventRepository.class); + + private final AuditEventsWebEndpointExtension extension = new AuditEventsWebEndpointExtension( + new AuditEventsEndpoint(this.repository)); + + private final AuditEvent event = new AuditEvent("principal", "type", + Collections.singletonMap("a", "alpha")); + + @Test + public void delegatesResponseIsAvailableFromEventsKeyInMap() { + Date date = new Date(); + given(this.repository.find("principal", date, "type")) + .willReturn(Collections.singletonList(this.event)); + Map> result = this.extension + .eventsWithPrincipalDateAfterAndType("principal", date, "type"); + assertThat(result).hasSize(1); + assertThat(result).containsEntry("events", Collections.singletonList(this.event)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EnvironmentEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EnvironmentEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..ca55c7f9c5 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EnvironmentEndpointWebIntegrationTests.java @@ -0,0 +1,136 @@ +/* + * 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.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; + +@RunWith(WebEndpointsRunner.class) +public class EnvironmentEndpointWebIntegrationTests { + + private static WebTestClient client; + + private static ConfigurableApplicationContext context; + + @Before + public void prepareEnvironment() { + TestPropertyValues.of("foo:bar", "fool:baz").applyTo(context); + } + + @Test + public void home() throws Exception { + client.get().uri("/application/env").exchange().expectStatus().isOk().expectBody() + .jsonPath("propertySources[?(@.name=='systemProperties')]").exists(); + } + + @Test + public void sub() throws Exception { + client.get().uri("/application/env/foo").exchange().expectStatus().isOk() + .expectBody().jsonPath(forProperty("test", "foo")).isEqualTo("bar"); + } + + @Test + public void regex() throws Exception { + Map map = new HashMap<>(); + map.put("food", null); + EnvironmentEndpointWebIntegrationTests.context.getEnvironment() + .getPropertySources().addFirst(new MapPropertySource("null-value", map)); + client.get().uri("/application/env?pattern=foo.*").exchange().expectStatus() + .isOk().expectBody().jsonPath(forProperty("test", "foo")).isEqualTo("bar") + .jsonPath(forProperty("test", "fool")).isEqualTo("baz"); + } + + @Test + public void nestedPathWhenPlaceholderCannotBeResolvedShouldReturnUnresolvedProperty() + throws Exception { + Map map = new HashMap(); + map.put("my.foo", "${my.bar}"); + context.getEnvironment().getPropertySources() + .addFirst(new MapPropertySource("unresolved-placeholder", map)); + client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk() + .expectBody().jsonPath(forProperty("unresolved-placeholder", "my.foo")) + .isEqualTo("${my.bar}"); + } + + @Test + public void nestedPathWithSensitivePlaceholderShouldSanitize() throws Exception { + Map map = new HashMap(); + map.put("my.foo", "${my.password}"); + map.put("my.password", "hello"); + context.getEnvironment().getPropertySources() + .addFirst(new MapPropertySource("placeholder", map)); + client.get().uri("/application/env/my.foo").exchange().expectStatus().isOk() + .expectBody().jsonPath(forProperty("placeholder", "my.foo")) + .isEqualTo("******"); + } + + @Test + public void nestedPathMatchedByRegexWhenPlaceholderCannotBeResolvedShouldReturnUnresolvedProperty() + throws Exception { + Map map = new HashMap(); + map.put("my.foo", "${my.bar}"); + context.getEnvironment().getPropertySources() + .addFirst(new MapPropertySource("unresolved-placeholder", map)); + client.get().uri("/application/env?pattern=my.*").exchange().expectStatus().isOk() + .expectBody() + .jsonPath( + "propertySources[?(@.name=='unresolved-placeholder')].properties.['my.foo'].value") + .isEqualTo("${my.bar}"); + } + + @Test + public void nestedPathMatchedByRegexWithSensitivePlaceholderShouldSanitize() + throws Exception { + Map map = new HashMap(); + map.put("my.foo", "${my.password}"); + map.put("my.password", "hello"); + context.getEnvironment().getPropertySources() + .addFirst(new MapPropertySource("placeholder", map)); + client.get().uri("/application/env?pattern=my.*").exchange().expectStatus().isOk() + .expectBody().jsonPath(forProperty("placeholder", "my.foo")) + .isEqualTo("******"); + } + + private String forProperty(String source, String name) { + return "propertySources[?(@.name=='" + source + "')].properties.['" + name + + "'].value"; + } + + @Configuration + static class TestConfiguration { + + @Bean + public EnvironmentEndpoint endpoint(Environment environment) { + return new EnvironmentEndpoint(environment); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..6b063eb327 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java @@ -0,0 +1,108 @@ +/* + * 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.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link HealthEndpoint} and {@link HealthWebEndpointExtension} + * exposed by Jersey, Spring MVC, and WebFlux. + * + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class HealthEndpointWebIntegrationTests { + + private static WebTestClient client; + + private static ConfigurableApplicationContext context; + + @Test + public void whenHealthIsUp200ResponseIsReturned() throws Exception { + client.get().uri("/application/health").exchange().expectStatus().isOk() + .expectBody().jsonPath("status").isEqualTo("UP").jsonPath("alpha.status") + .isEqualTo("UP").jsonPath("bravo.status").isEqualTo("UP"); + } + + @Test + public void whenHealthIsDown503ResponseIsReturned() throws Exception { + context.getBean("alphaHealthIndicator", TestHealthIndicator.class) + .setHealth(Health.down().build()); + client.get().uri("/application/health").exchange().expectStatus() + .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status") + .isEqualTo("DOWN").jsonPath("alpha.status").isEqualTo("DOWN") + .jsonPath("bravo.status").isEqualTo("UP"); + } + + @Configuration + public static class TestConfiguration { + + @Bean + public HealthEndpoint healthEndpoint( + Map healthIndicators) { + return new HealthEndpoint(new OrderedHealthAggregator(), healthIndicators); + } + + @Bean + public HealthWebEndpointExtension healthWebEndpointExtension( + HealthEndpoint delegate) { + return new HealthWebEndpointExtension(delegate); + } + + @Bean + public TestHealthIndicator alphaHealthIndicator() { + return new TestHealthIndicator(); + } + + @Bean + public TestHealthIndicator bravoHealthIndicator() { + return new TestHealthIndicator(); + } + + } + + private static class TestHealthIndicator implements HealthIndicator { + + private Health health = Health.up().build(); + + @Override + public Health health() { + Health result = this.health; + this.health = Health.up().build(); + return result; + } + + void setHealth(Health health) { + this.health = health; + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointTests.java new file mode 100644 index 0000000000..6ee9fda1bb --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointTests.java @@ -0,0 +1,57 @@ +/* + * 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.concurrent.CountDownLatch; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HeapDumpWebEndpoint}. + * + * @author Andy Wilkinson + */ +public class HeapDumpWebEndpointTests { + + @Test + public void parallelRequestProducesTooManyRequestsResponse() + throws InterruptedException { + CountDownLatch dumpingLatch = new CountDownLatch(1); + CountDownLatch blockingLatch = new CountDownLatch(1); + HeapDumpWebEndpoint slowEndpoint = new HeapDumpWebEndpoint(2500) { + + @Override + protected HeapDumper createHeapDumper() + throws HeapDumperUnavailableException { + return (file, live) -> { + dumpingLatch.countDown(); + blockingLatch.await(); + }; + } + + }; + Thread thread = new Thread(() -> slowEndpoint.heapDump(true)); + thread.start(); + dumpingLatch.await(); + assertThat(slowEndpoint.heapDump(true).getStatus()).isEqualTo(429); + blockingLatch.countDown(); + thread.join(); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..866bb584c4 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HeapDumpWebEndpointWebIntegrationTests.java @@ -0,0 +1,123 @@ +/* + * 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.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.FileCopyUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link HeapDumpWebEndpoint} exposed by Jersey, Spring MVC, and + * WebFlux. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class HeapDumpWebEndpointWebIntegrationTests { + + private static WebTestClient client; + + private static ConfigurableApplicationContext context; + + private TestHeapDumpWebEndpoint endpoint; + + @Before + public void configureEndpoint() { + this.endpoint = context.getBean(TestHeapDumpWebEndpoint.class); + this.endpoint.setAvailable(true); + } + + @Test + public void invokeWhenNotAvailableShouldReturnServiceUnavailableStatus() + throws Exception { + this.endpoint.setAvailable(false); + client.get().uri("/application/heapdump").exchange().expectStatus() + .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); + } + + @Test + public void getRequestShouldReturnHeapDumpInResponseBody() throws Exception { + client.get().uri("/application/heapdump").exchange().expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_OCTET_STREAM) + .expectBody(String.class).isEqualTo("HEAPDUMP"); + assertThat(this.endpoint.file.exists()).isFalse(); + } + + @Configuration + public static class TestConfiguration { + + @Bean + public HeapDumpWebEndpoint endpoint() { + return new TestHeapDumpWebEndpoint(); + } + + } + + private static class TestHeapDumpWebEndpoint extends HeapDumpWebEndpoint { + + private boolean available; + + private String heapDump = "HEAPDUMP"; + + private File file; + + TestHeapDumpWebEndpoint() { + super(TimeUnit.SECONDS.toMillis(1)); + reset(); + } + + public void reset() { + this.available = true; + } + + @Override + protected HeapDumper createHeapDumper() { + return (file, live) -> { + this.file = file; + if (!TestHeapDumpWebEndpoint.this.available) { + throw new HeapDumperUnavailableException("Not available", null); + } + if (file.exists()) { + throw new IOException("File exists"); + } + FileCopyUtils.copy(TestHeapDumpWebEndpoint.this.heapDump.getBytes(), + file); + }; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/InfoEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/InfoEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..edd25bf5ad --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/InfoEndpointWebIntegrationTests.java @@ -0,0 +1,86 @@ +/* + * 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.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.InfoEndpoint; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link InfoEndpoint} exposed by Jersey, Spring MVC, and WebFlux. + * + * @author Meang Akira Tanaka + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +@TestPropertySource(properties = { "info.app.name=MyService" }) +public class InfoEndpointWebIntegrationTests { + + private static WebTestClient client; + + @Test + public void info() throws Exception { + client.get().uri("/application/info").accept(MediaType.APPLICATION_JSON) + .exchange().expectStatus().isOk().expectBody().jsonPath("beanName1.key11") + .isEqualTo("value11").jsonPath("beanName1.key12").isEqualTo("value12") + .jsonPath("beanName2.key21").isEqualTo("value21") + .jsonPath("beanName2.key22").isEqualTo("value22"); + } + + @Configuration + public static class TestConfiguration { + + @Bean + public InfoEndpoint endpoint() { + return new InfoEndpoint(Arrays.asList(beanName1(), beanName2())); + } + + @Bean + public InfoContributor beanName1() { + return (builder) -> { + Map content = new LinkedHashMap<>(); + content.put("key11", "value11"); + content.put("key12", "value12"); + builder.withDetail("beanName1", content); + }; + } + + @Bean + public InfoContributor beanName2() { + return (builder) -> { + Map content = new LinkedHashMap<>(); + content.put("key21", "value21"); + content.put("key22", "value22"); + builder.withDetail("beanName2", content); + }; + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointTests.java new file mode 100644 index 0000000000..8b4e428c13 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointTests.java @@ -0,0 +1,88 @@ +/* + * 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.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.springframework.core.io.Resource; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StreamUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LogFileWebEndpoint}. + * + * @author Johannes Edmeier + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class LogFileWebEndpointTests { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private final MockEnvironment environment = new MockEnvironment(); + + private final LogFileWebEndpoint endpoint = new LogFileWebEndpoint(this.environment); + + private File logFile; + + @Before + public void before() throws IOException { + this.logFile = this.temp.newFile(); + FileCopyUtils.copy("--TEST--".getBytes(), this.logFile); + } + + @Test + public void nullResponseWithoutLogFile() throws Exception { + assertThat(this.endpoint.logFile()).isNull(); + } + + @Test + public void nullResponseWithMissingLogFile() throws Exception { + this.environment.setProperty("logging.file", "no_test.log"); + assertThat(this.endpoint.logFile()).isNull(); + } + + @Test + public void resourceResponseWithLogFile() throws Exception { + this.environment.setProperty("logging.file", this.logFile.getAbsolutePath()); + Resource resource = this.endpoint.logFile(); + assertThat(resource).isNotNull(); + assertThat(StreamUtils.copyToString(resource.getInputStream(), + StandardCharsets.UTF_8)).isEqualTo("--TEST--"); + } + + @Test + public void resourceResponseWithExternalLogFile() throws Exception { + this.endpoint.setExternalFile(this.logFile); + Resource resource = this.endpoint.logFile(); + assertThat(resource).isNotNull(); + assertThat(StreamUtils.copyToString(resource.getInputStream(), + StandardCharsets.UTF_8)).isEqualTo("--TEST--"); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..42d6d10e94 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LogFileWebEndpointWebIntegrationTests.java @@ -0,0 +1,83 @@ +/* + * 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.io.File; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.FileCopyUtils; + +/** + * Integration tests for {@link LogFileWebEndpoint} exposed by Jersey, Spring MVC, and + * WebFlux. + * + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class LogFileWebEndpointWebIntegrationTests { + + private static ConfigurableApplicationContext context; + + private static WebTestClient client; + + @Rule + public final TemporaryFolder temp = new TemporaryFolder(); + + private File logFile; + + @Before + public void setUp() throws IOException { + this.logFile = this.temp.newFile(); + FileCopyUtils.copy("--TEST--".getBytes(), this.logFile); + } + + @Test + public void getRequestProduces404ResponseWhenLogFileNotFound() throws Exception { + client.get().uri("/application/logfile").exchange().expectStatus().isNotFound(); + } + + @Test + public void getRequestProducesResponseWithLogFile() throws Exception { + TestPropertyValues.of("logging.file:" + this.logFile.getAbsolutePath()) + .applyTo(context); + client.get().uri("/application/logfile").exchange().expectStatus().isOk() + .expectBody(String.class).isEqualTo("--TEST--"); + } + + @Configuration + static class TestConfiguration { + + @Bean + public LogFileWebEndpoint logFileEndpoint(Environment environment) { + return new LogFileWebEndpoint(environment); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LoggersEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LoggersEndpointWebIntegrationTests.java new file mode 100644 index 0000000000..a7d9f6dee0 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/LoggersEndpointWebIntegrationTests.java @@ -0,0 +1,180 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.EnumSet; + +import net.minidev.json.JSONArray; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ActuatorMediaTypes; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.LoggingSystem; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Integration tests for {@link LoggersEndpoint} when exposed via Jersey, Spring MVC, and + * WebFlux. + * + * @author Ben Hale + * @author Phillip Webb + * @author EddĂș MelĂ©ndez + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +@RunWith(WebEndpointsRunner.class) +public class LoggersEndpointWebIntegrationTests { + + private static ConfigurableApplicationContext context; + + private static WebTestClient client; + + private LoggingSystem loggingSystem; + + @Before + @After + public void resetMocks() { + this.loggingSystem = context.getBean(LoggingSystem.class); + Mockito.reset(this.loggingSystem); + given(this.loggingSystem.getSupportedLogLevels()) + .willReturn(EnumSet.allOf(LogLevel.class)); + } + + @Test + public void getLoggerShouldReturnAllLoggerConfigurations() throws Exception { + given(this.loggingSystem.getLoggerConfigurations()).willReturn(Collections + .singletonList(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG))); + client.get().uri("/application/loggers").exchange().expectStatus().isOk() + .expectBody().jsonPath("$.length()").isEqualTo(2).jsonPath("levels") + .isEqualTo(jsonArrayOf("OFF", "FATAL", "ERROR", "WARN", "INFO", "DEBUG", + "TRACE")) + .jsonPath("loggers.length()").isEqualTo(1) + .jsonPath("loggers.ROOT.length()").isEqualTo(2) + .jsonPath("loggers.ROOT.configuredLevel").isEqualTo(null) + .jsonPath("loggers.ROOT.effectiveLevel").isEqualTo("DEBUG"); + } + + @Test + public void getLoggerShouldReturnLogLevels() throws Exception { + given(this.loggingSystem.getLoggerConfiguration("ROOT")) + .willReturn(new LoggerConfiguration("ROOT", null, LogLevel.DEBUG)); + client.get().uri("/application/loggers/ROOT").exchange().expectStatus().isOk() + .expectBody().jsonPath("$.length()").isEqualTo(2) + .jsonPath("configuredLevel").isEqualTo(null).jsonPath("effectiveLevel") + .isEqualTo("DEBUG"); + } + + @Test + public void getLoggersWhenLoggerNotFoundShouldReturnNotFound() throws Exception { + client.get().uri("/application/loggers/com.does.not.exist").exchange() + .expectStatus().isNotFound(); + } + + @Test + public void setLoggerUsingApplicationJsonShouldSetLogLevel() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(MediaType.APPLICATION_JSON) + .syncBody(Collections.singletonMap("configuredLevel", "debug")).exchange() + .expectStatus().isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); + } + + @Test + public void setLoggerUsingActuatorV2JsonShouldSetLogLevel() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON) + .syncBody(Collections.singletonMap("configuredLevel", "debug")).exchange() + .expectStatus().isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG); + } + + @Test + public void setLoggerWithWrongLogLevelResultInBadRequestResponse() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(MediaType.APPLICATION_JSON) + .syncBody(Collections.singletonMap("configuredLevel", "other")).exchange() + .expectStatus().isBadRequest(); + verifyZeroInteractions(this.loggingSystem); + } + + @Test + public void setLoggerWithNullLogLevel() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON) + .syncBody(Collections.singletonMap("configuredLevel", null)).exchange() + .expectStatus().isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", null); + } + + @Test + public void setLoggerWithNoLogLevel() throws Exception { + client.post().uri("/application/loggers/ROOT") + .contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V2_JSON) + .syncBody(Collections.emptyMap()).exchange().expectStatus().isNoContent(); + verify(this.loggingSystem).setLogLevel("ROOT", null); + } + + @Test + public void logLevelForLoggerWithNameThatCouldBeMistakenForAPathExtension() + throws Exception { + given(this.loggingSystem.getLoggerConfiguration("com.png")) + .willReturn(new LoggerConfiguration("com.png", null, LogLevel.DEBUG)); + client.get().uri("/application/loggers/com.png").exchange().expectStatus().isOk() + .expectBody().jsonPath("$.length()").isEqualTo(2) + .jsonPath("configuredLevel").isEqualTo(null).jsonPath("effectiveLevel") + .isEqualTo("DEBUG"); + } + + private JSONArray jsonArrayOf(Object... entries) { + JSONArray array = new JSONArray(); + array.addAll(Arrays.asList(entries)); + return array; + } + + @Configuration + static class TestConfiguration { + + @Bean + public LoggingSystem loggingSystem() { + return mock(LoggingSystem.class); + } + + @Bean + public LoggersEndpoint endpoint(LoggingSystem loggingSystem) { + return new LoggersEndpoint(loggingSystem); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/MetricsEndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/MetricsEndpointMvcIntegrationTests.java new file mode 100644 index 0000000000..27d8f1fd6e --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/MetricsEndpointMvcIntegrationTests.java @@ -0,0 +1,111 @@ +/* + * 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.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.MetricsEndpoint; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration tests for {@link MetricsEndpoint} when exposed via Spring MVC + * + * @author Andy Wilkinson + * @author Sergei Egorov + */ +@RunWith(WebEndpointsRunner.class) +public class MetricsEndpointMvcIntegrationTests { + + private static WebTestClient client; + + @Test + public void home() { + client.get().uri("/application/metrics").exchange().expectStatus().isOk() + .expectBody().jsonPath("foo").isEqualTo(1); + } + + @Test + public void specificMetric() { + client.get().uri("/application/metrics/foo").exchange().expectStatus().isOk() + .expectBody().jsonPath("foo").isEqualTo(1); + } + + @Test + public void specificMetricWithDot() throws Exception { + client.get().uri("/application/metrics/group2.a").exchange().expectStatus().isOk() + .expectBody().jsonPath("$.length()").isEqualTo(1).jsonPath("['group2.a']") + .isEqualTo("1"); + } + + @Test + public void specificMetricWithNameThatCouldBeMistakenForAPathExtension() { + client.get().uri("/application/metrics/bar.png").exchange().expectStatus().isOk() + .expectBody().jsonPath("['bar.png']").isEqualTo(1); + } + + @Test + public void specificMetricThatDoesNotExist() throws Exception { + client.get().uri("/application/metrics/bar").exchange().expectStatus() + .isNotFound(); + } + + @Test + public void regexAll() throws Exception { + client.get().uri("/application/metrics?pattern=.*").exchange().expectStatus() + .isOk().expectBody().jsonPath("$.length()").isEqualTo(6).jsonPath("foo") + .isEqualTo(1).jsonPath("['bar.png']").isEqualTo(1) + .jsonPath("['group1.a']").isEqualTo(1).jsonPath("['group1.b']") + .isEqualTo(1).jsonPath("['group2.a']").isEqualTo(1).jsonPath("group2_a") + .isEqualTo(1); + } + + @Test + public void regexGroupDot() throws Exception { + client.get().uri("/application/metrics?pattern=group%5B0-9%5D%5C..*").exchange() + .expectStatus().isOk().expectBody().jsonPath("$.length()").isEqualTo(3) + .jsonPath("['group1.a']").isEqualTo(1).jsonPath("['group1.b']") + .isEqualTo(1).jsonPath("['group2.a']").isEqualTo(1); + } + + @Test + public void regexGroup1() throws Exception { + client.get().uri("/application/metrics?pattern=group1%5C..*").exchange() + .expectStatus().isOk().expectBody().jsonPath("['group1.a']").isEqualTo(1) + .jsonPath("['group1.b']").isEqualTo(1).jsonPath("$.length()") + .isEqualTo(2); + } + + @Configuration + static class TestConfiguration { + + @Bean + public MetricsEndpoint endpoint() { + return new MetricsEndpoint(() -> Arrays.asList(new Metric<>("foo", 1), + new Metric<>("bar.png", 1), new Metric<>("group1.a", 1), + new Metric<>("group1.b", 1), new Metric<>("group2.a", 1), + new Metric<>("group2_a", 1), new Metric("baz", null))); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebEndpointsRunner.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebEndpointsRunner.java new file mode 100644 index 0000000000..43799b6fa0 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebEndpointsRunner.java @@ -0,0 +1,408 @@ +/* + * 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.lang.reflect.Modifier; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.runner.Runner; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Suite; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; + +import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.ApplicationContext; +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.annotation.Import; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; +import org.springframework.web.util.DefaultUriBuilderFactory; +import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; + +/** + * A custom {@link Runner} that tests web endpoints that are made available over HTTP + * using Jersey, Spring MVC, and WebFlux. + *

+ * The following types can be automatically injected into static fields on the test class: + *

    + *
  • {@link WebTestClient}
  • + *
  • {@link ConfigurableApplicationContext}
  • + *
+ *

+ * The {@link PropertySource PropertySources} that belong to the application context's + * {@link org.springframework.core.env.Environment} are reset at the end of every test. + * This means that {@link TestPropertyValues} can be used in a test without affecting the + * {@code Environment} of other tests in the same class. + * + * @author Andy Wilkinson + */ +public class WebEndpointsRunner extends Suite { + + public WebEndpointsRunner(Class testClass) throws InitializationError { + super(testClass, createRunners(testClass)); + } + + private static List createRunners(Class clazz) throws InitializationError { + return Arrays.asList(new JerseyWebEndpointsRunner(clazz), + new MvcWebEndpointsRunner(clazz), new ReactiveWebEndpointsRunner(clazz)); + } + + private static class AbstractWebEndpointsRunner extends BlockJUnit4ClassRunner { + + private final TestContext testContext; + + private final String name; + + protected AbstractWebEndpointsRunner(Class klass, String name, + Function>, ConfigurableApplicationContext> contextLoader) + throws InitializationError { + super(klass); + this.name = name; + this.testContext = new TestContext(klass, contextLoader); + } + + @Override + protected final String getName() { + return this.name; + } + + @Override + protected String testName(FrameworkMethod method) { + return super.testName(method) + "[" + getName() + "]"; + } + + @Override + protected Statement withBeforeClasses(Statement statement) { + Statement delegate = super.withBeforeClasses(statement); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + AbstractWebEndpointsRunner.this.testContext.beforeClass(); + delegate.evaluate(); + } + + }; + } + + @Override + protected Statement withAfterClasses(Statement statement) { + Statement delegate = super.withAfterClasses(statement); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + try { + delegate.evaluate(); + } + finally { + AbstractWebEndpointsRunner.this.testContext.afterClass(); + } + } + + }; + } + + @Override + protected Statement withBefores(FrameworkMethod method, Object target, + Statement statement) { + Statement delegate = super.withBefores(method, target, statement); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + AbstractWebEndpointsRunner.this.testContext.beforeTest(); + delegate.evaluate(); + } + + }; + } + + @Override + protected Statement withAfters(FrameworkMethod method, Object target, + Statement statement) { + Statement delegate = super.withAfters(method, target, statement); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + try { + delegate.evaluate(); + } + finally { + AbstractWebEndpointsRunner.this.testContext.afterTest(); + } + } + + }; + } + + } + + private static final class MvcWebEndpointsRunner extends AbstractWebEndpointsRunner { + + private MvcWebEndpointsRunner(Class klass) throws InitializationError { + super(klass, "Spring MVC", (classes) -> { + AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); + classes.add(MvcTestConfiguration.class); + context.register(classes.toArray(new Class[classes.size()])); + context.refresh(); + return context; + }); + } + + @Configuration + @ImportAutoConfiguration({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletEndpointAutoConfiguration.class }) + static class MvcTestConfiguration { + + @Bean + public TomcatServletWebServerFactory tomcat() { + return new TomcatServletWebServerFactory(0); + } + + } + + } + + private static final class JerseyWebEndpointsRunner + extends AbstractWebEndpointsRunner { + + private JerseyWebEndpointsRunner(Class klass) throws InitializationError { + super(klass, "Jersey", (classes) -> { + AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); + classes.add(JerseyAppConfiguration.class); + classes.add(JerseyInfrastructureConfiguration.class); + context.register(classes.toArray(new Class[classes.size()])); + context.refresh(); + return context; + }); + } + + @Configuration + @Import({ JacksonAutoConfiguration.class, JerseyAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class }) + static class JerseyInfrastructureConfiguration { + + @Bean + public TomcatServletWebServerFactory tomcat() { + return new TomcatServletWebServerFactory(0); + } + + } + + @Configuration + static class JerseyAppConfiguration { + + @Bean + public ResourceConfig resourceConfig() { + return new ResourceConfig(); + } + + } + + } + + private static final class ReactiveWebEndpointsRunner + extends AbstractWebEndpointsRunner { + + private ReactiveWebEndpointsRunner(Class klass) throws InitializationError { + super(klass, "Reactive", (classes) -> { + ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext(); + classes.add(ReactiveInfrastructureConfiguration.class); + context.register(classes.toArray(new Class[classes.size()])); + context.refresh(); + return context; + }); + } + + @Configuration + @ImportAutoConfiguration({ JacksonAutoConfiguration.class, + WebFluxAutoConfiguration.class, + EndpointInfrastructureAutoConfiguration.class, + ManagementContextAutoConfiguration.class }) + static class ReactiveInfrastructureConfiguration + implements ApplicationListener { + + @Bean + public NettyReactiveWebServerFactory netty() { + return new NettyReactiveWebServerFactory(0); + } + + @Override + public void onApplicationEvent(WebServerInitializedEvent event) { + portHolder().setPort(event.getWebServer().getPort()); + } + + @Bean + public HttpHandler httpHandler(ApplicationContext applicationContext) { + return WebHttpHandlerBuilder.applicationContext(applicationContext) + .build(); + } + + @Bean + public PortHolder portHolder() { + return new PortHolder(); + } + + } + + } + + private static final class PortHolder { + + private int port; + + int getPort() { + return this.port; + } + + void setPort(int port) { + this.port = port; + } + + } + + private static final class TestContext { + + private final Class testClass; + + private final Function>, ConfigurableApplicationContext> applicationContextLoader; + + private ConfigurableApplicationContext applicationContext; + + private List> propertySources; + + TestContext(Class testClass, + Function>, ConfigurableApplicationContext> applicationContextLoader) { + this.testClass = testClass; + this.applicationContextLoader = applicationContextLoader; + } + + private void beforeClass() { + this.applicationContext = createApplicationContext(); + WebTestClient webTestClient = createWebTestClient(); + injectIfPossible(this.testClass, webTestClient); + injectIfPossible(this.testClass, this.applicationContext); + } + + private void beforeTest() { + capturePropertySources(); + } + + private void afterTest() { + restorePropertySources(); + } + + private void afterClass() { + if (this.applicationContext != null) { + this.applicationContext.close(); + } + } + + private ConfigurableApplicationContext createApplicationContext() { + return this.applicationContextLoader + .apply(new ArrayList<>(Stream.of(this.testClass.getDeclaredClasses()) + .filter((candidate) -> AnnotationUtils.findAnnotation( + candidate, Configuration.class) != null) + .collect(Collectors.toList()))); + } + + private WebTestClient createWebTestClient() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory( + "http://localhost:" + determinePort()); + uriBuilderFactory.setEncodingMode(EncodingMode.NONE); + WebTestClient webTestClient = WebTestClient.bindToServer() + .uriBuilderFactory(uriBuilderFactory) + .responseTimeout(Duration.ofSeconds(30)).build(); + return webTestClient; + } + + private int determinePort() { + if (this.applicationContext instanceof AnnotationConfigServletWebServerApplicationContext) { + return ((AnnotationConfigServletWebServerApplicationContext) this.applicationContext) + .getWebServer().getPort(); + } + return this.applicationContext.getBean(PortHolder.class).getPort(); + } + + private void injectIfPossible(Class target, Object value) { + ReflectionUtils.doWithFields(target, (field) -> { + if (Modifier.isStatic(field.getModifiers()) + && field.getType().isInstance(value)) { + ReflectionUtils.makeAccessible(field); + ReflectionUtils.setField(field, null, value); + } + }); + } + + private void capturePropertySources() { + this.propertySources = new ArrayList<>(); + this.applicationContext.getEnvironment().getPropertySources() + .forEach(this.propertySources::add); + } + + private void restorePropertySources() { + List names = new ArrayList<>(); + MutablePropertySources propertySources = this.applicationContext + .getEnvironment().getPropertySources(); + propertySources + .forEach((propertySource) -> names.add(propertySource.getName())); + names.forEach(propertySources::remove); + this.propertySources.forEach(propertySources::addLast); + } + + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 016604ebc0..563c71fe9c 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1085,63 +1085,31 @@ content into your application; rather pick only the properties that you need. # ENDPOINTS ({sc-spring-boot-actuator}/endpoint/AbstractEndpoint.{sc-ext}[AbstractEndpoint] subclasses) endpoints.enabled=true # Enable endpoints. endpoints.auditevents.enabled= # Enable the endpoint. - endpoints.auditevents.path= # Endpoint path. endpoints.autoconfig.enabled= # Enable the endpoint. - endpoints.autoconfig.id= # Endpoint identifier. - endpoints.autoconfig.path= # Endpoint path. endpoints.beans.enabled= # Enable the endpoint. - endpoints.beans.id= # Endpoint identifier. - endpoints.beans.path= # Endpoint path. endpoints.configprops.enabled= # Enable the endpoint. - endpoints.configprops.id= # Endpoint identifier. endpoints.configprops.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regex expressions. - endpoints.configprops.path= # Endpoint path. - endpoints.docs.curies.enabled=false # Enable the curie generation. - endpoints.docs.enabled=true # Enable actuator docs endpoint. - endpoints.docs.path=/docs # - endpoints.dump.enabled= # Enable the endpoint. - endpoints.dump.id= # Endpoint identifier. - endpoints.dump.path= # Endpoint path. endpoints.env.enabled= # Enable the endpoint. - endpoints.env.id= # Endpoint identifier. endpoints.env.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regex expressions. - endpoints.env.path= # Endpoint path. endpoints.flyway.enabled= # Enable the endpoint. - endpoints.flyway.id= # Endpoint identifier. endpoints.health.enabled= # Enable the endpoint. - endpoints.health.id= # Endpoint identifier. endpoints.health.mapping.*= # Mapping of health statuses to HttpStatus codes. By default, registered health statuses map to sensible defaults (i.e. UP maps to 200). - endpoints.health.path= # Endpoint path. - endpoints.health.time-to-live=1000 # Time to live for cached result, in milliseconds. endpoints.heapdump.enabled= # Enable the endpoint. - endpoints.heapdump.path= # Endpoint path. endpoints.info.enabled= # Enable the endpoint. - endpoints.info.id= # Endpoint identifier. - endpoints.info.path= # Endpoint path. endpoints.liquibase.enabled= # Enable the endpoint. - endpoints.liquibase.id= # Endpoint identifier. endpoints.logfile.enabled=true # Enable the endpoint. endpoints.logfile.external-file= # External Logfile to be accessed. endpoints.logfile.path=/logfile # Endpoint URL path. endpoints.loggers.enabled=true # Enable the endpoint. - endpoints.loggers.id= # Endpoint identifier. - endpoints.loggers.path=/logfile # Endpoint path. endpoints.mappings.enabled= # Enable the endpoint. - endpoints.mappings.id= # Endpoint identifier. - endpoints.mappings.path= # Endpoint path. endpoints.metrics.enabled= # Enable the endpoint. endpoints.metrics.filter.enabled=true # Enable the metrics servlet filter. endpoints.metrics.filter.gauge-submissions=merged # Http filter gauge submissions (merged, per-http-method) endpoints.metrics.filter.counter-submissions=merged # Http filter counter submissions (merged, per-http-method) - endpoints.metrics.id= # Endpoint identifier. - endpoints.metrics.path= # Endpoint path. endpoints.shutdown.enabled= # Enable the endpoint. - endpoints.shutdown.id= # Endpoint identifier. - endpoints.shutdown.path= # Endpoint path. + endpoints.threaddump.enabled= # Enable the endpoint. endpoints.trace.enabled= # Enable the endpoint. endpoints.trace.filter.enabled=true # Enable the trace servlet filter. - endpoints.trace.id= # Endpoint identifier. - endpoints.trace.path= # Endpoint path. # ENDPOINTS CORS CONFIGURATION ({sc-spring-boot-actuator}/autoconfigure/EndpointCorsProperties.{sc-ext}[EndpointCorsProperties]) endpoints.cors.allow-credentials= # Set whether credentials are supported. When not set, credentials are not supported. diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 8b432fb424..764aa98648 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -65,8 +65,8 @@ rules of thumb: `+@Conditional*+` annotations to find out what features they enable and when. Add `--debug` to the command line or a System property `-Ddebug` to get a log on the console of all the auto-configuration decisions that were made in your app. In a running - Actuator app look at the `autoconfig` endpoint ('`/autoconfig`' or the JMX equivalent) for - the same information. + Actuator app look at the `autoconfig` endpoint (`/application/autoconfig` or the JMX + equivalent) for the same information. * Look for classes that are `@ConfigurationProperties` (e.g. {sc-spring-boot-autoconfigure}/web/ServerProperties.{sc-ext}[`ServerProperties`]) and read from there the available external configuration options. The diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 15402b9176..a459d12eb9 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -82,9 +82,6 @@ The following technology agnostic endpoints are available: |`configprops` |Displays a collated list of all `@ConfigurationProperties`. -|`dump` -|Performs a thread dump. - |`env` |Exposes properties from Spring's `ConfigurableEnvironment`. @@ -112,11 +109,15 @@ The following technology agnostic endpoints are available: |`shutdown` |Allows the application to be gracefully shutdown (not enabled by default). +|`threaddump` +|Performs a thread dump. + |`trace` |Displays trace information (by default the last 100 HTTP requests). |=== -If you are using Spring MVC, the following additional endpoints can also be used: +If your application is a web application (Spring MVC, Spring WebFlux, or Jersey), the +following additional endpoints can also be used: [cols="2,5"] |=== @@ -234,8 +235,9 @@ from `/management`. === CORS support http://en.wikipedia.org/wiki/Cross-origin_resource_sharing[Cross-origin resource sharing] (CORS) is a http://www.w3.org/TR/cors/[W3C specification] that allows you to specify in a -flexible way what kind of cross domain requests are authorized. Actuator's MVC endpoints -can be configured to support such scenarios. +flexible way what kind of cross domain requests are authorized. If you are using Spring +MVC or Spring WebFlux, Actuator's web endpoints can be configured to support such +scenarios. CORS support is disabled by default and is only enabled once the `endpoints.cors.allowed-origins` property has been set. The configuration below permits @@ -254,18 +256,15 @@ for a complete list of options. [[production-ready-customizing-endpoints-programmatically]] === Adding custom endpoints -If you add a `@Bean` of type `Endpoint` then it will automatically be exposed over JMX and -HTTP (if there is an server available). An HTTP endpoints can be customized further by -creating a bean of type `MvcEndpoint`. Your `MvcEndpoint` is not a `@Controller` but it -can use `@RequestMapping` (and `@Managed*`) to expose resources. +If you add a `@Bean` annotated with `@Endpoint`, any methods annotated with +`@ReadOperation` or `@WriteOperation` will automatically be exposed over JMX and, in a web +application, over HTTP as well. TIP: If you are doing this as a library feature consider adding a configuration class annotated with `@ManagementContextConfiguration` to `/META-INF/spring.factories` under the key `org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration`. If -you do that then the endpoint will move to a child context with all the other MVC -endpoints if your users ask for a separate management port or address. A configuration -declared this way can be a `WebConfigurerAdapter` if it wants to add static resources (for -instance) to the management endpoints. +you do that then the endpoint will move to a child context with all the other web +endpoints endpoints if your users ask for a separate management port or address. @@ -289,13 +288,6 @@ overall health status. If no `HealthIndicator` returns a status that is known to -=== Security with HealthIndicators -Health responses are also cached to prevent "`denial of service`" attacks. Use the -`endpoints.health.time-to-live` property if you want to change the default cache period -of 1000 milliseconds. - - - ==== Auto-configured HealthIndicators The following `HealthIndicators` are auto-configured by Spring Boot when appropriate: @@ -561,7 +553,8 @@ is exposed as `/application/health`. === Customizing the management endpoint paths Sometimes it is useful to customize the prefix for the management endpoints. For example, your application might already use `/application` for another purpose. -You can use the `management.context-path` property to change the prefix for your management endpoint: +You can use the `management.context-path` property to change the prefix for your +management endpoint: [source,properties,indent=0] ---- @@ -571,21 +564,9 @@ You can use the `management.context-path` property to change the prefix for your The `application.properties` example above will change the endpoint from `/application/{id}` to `/manage/{id}` (e.g. `/manage/info`). -You can also change the "`path`" of an endpoint (using `endpoints.{name}.path`) which then -changes the default resource path for the MVC endpoint. There is no validation on -those values (so you can use anything that is legal in a URL path). For example, to change -the location of the `/health` endpoint to `/ping/me` you can set -`endpoints.health.path=/ping/me`. - NOTE: Even if an endpoint path is configured separately, it is still relative to the `management.context-path`. -TIP: If you provide a custom `MvcEndpoint` remember to include a settable `path` property, -and default it to `/{id}` if you want your code to behave like the standard MVC endpoints. -(Take a look at the `HealthMvcEndpoint` to see how you might do that.) If your custom -endpoint is an `Endpoint` (not an `MvcEndpoint`) then Spring Boot will take care of the -path for you. - [[production-ready-customizing-management-server-port]] @@ -686,10 +667,7 @@ The information exposed by the health endpoint varies depending on whether or no accessed anonymously, and whether or not the enclosing application is secure. By default, when accessed anonymously in a secure application, any details about the server's health are hidden and the endpoint will simply indicate whether or not the server -is up or down. Furthermore the response is cached for a configurable period to prevent the -endpoint being used in a denial of service attack. The `endpoints.health.time-to-live` -property is used to configure the caching period in milliseconds. It defaults to 1000, -i.e. one second. +is up or down. Sample summarized HTTP response (default for anonymous request): @@ -1429,7 +1407,7 @@ customize the file name and path via the `Writer` constructor. == Cloud Foundry support Spring Boot's actuator module includes additional support that is activated when you deploy to a compatible Cloud Foundry instance. The `/cloudfoundryapplication` path -provides an alternative secured route to all `NamedMvcEndpoint` beans. +provides an alternative secured route to all `@Endpoint` beans. The extended support allows Cloud Foundry management UIs (such as the web application that you can use to view deployed applications) to be augmented with Spring diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 0b0f537879..5db4040d1a 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1219,8 +1219,8 @@ early instantiation. There is a validation sample] so you can see how to set things up. TIP: The `spring-boot-actuator` module includes an endpoint that exposes all -`@ConfigurationProperties` beans. Simply point your web browser to `/configprops` -or use the equivalent JMX endpoint. See the +`@ConfigurationProperties` beans. Simply point your web browser to +`/application/configprops` or use the equivalent JMX endpoint. See the _<>_. section for details. diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/SampleController.java b/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/SampleController.java index 3cd2396e5e..838c3ddbf6 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/SampleController.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/SampleController.java @@ -24,6 +24,7 @@ import java.util.Map; import org.hibernate.validator.constraints.NotBlank; import org.springframework.context.annotation.Description; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -41,14 +42,14 @@ public class SampleController { this.helloWorldService = helloWorldService; } - @GetMapping("/") + @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Map hello() { return Collections.singletonMap("message", this.helloWorldService.getHelloMessage()); } - @PostMapping("/") + @PostMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Map olleh(@Validated Message message) { Map model = new LinkedHashMap<>(); diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java index 7c126fd539..8ca1ca437a 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java @@ -185,7 +185,8 @@ public class SampleActuatorApplicationTests { @Test public void traceWithParameterMap() throws Exception { - this.restTemplate.getForEntity("/application/health?param1=value1", String.class); + this.restTemplate.withBasicAuth("user", getPassword()) + .getForEntity("/application/health?param1=value1", String.class); @SuppressWarnings("rawtypes") ResponseEntity entity = this.restTemplate .withBasicAuth("user", getPassword()) diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ServletPathInsecureSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ServletPathInsecureSampleActuatorApplicationTests.java index cc3a44b6a3..f58f3b37a9 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ServletPathInsecureSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ServletPathInsecureSampleActuatorApplicationTests.java @@ -62,8 +62,8 @@ public class ServletPathInsecureSampleActuatorApplicationTests { @Test public void testMetricsIsSecure() throws Exception { @SuppressWarnings("rawtypes") - ResponseEntity entity = this.restTemplate.getForEntity("/spring//application/metrics", - Map.class); + ResponseEntity entity = this.restTemplate + .getForEntity("/spring/application/metrics", Map.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } diff --git a/spring-boot-samples/spring-boot-sample-web-method-security/src/test/java/sample/security/method/SampleMethodSecurityApplicationTests.java b/spring-boot-samples/spring-boot-sample-web-method-security/src/test/java/sample/security/method/SampleMethodSecurityApplicationTests.java index 818f729f12..27087525f2 100644 --- a/spring-boot-samples/spring-boot-sample-web-method-security/src/test/java/sample/security/method/SampleMethodSecurityApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-web-method-security/src/test/java/sample/security/method/SampleMethodSecurityApplicationTests.java @@ -105,8 +105,8 @@ public class SampleMethodSecurityApplicationTests { @Test public void testManagementProtected() throws Exception { - ResponseEntity entity = this.restTemplate.getForEntity("/application/beans", - String.class); + ResponseEntity entity = this.restTemplate + .getForEntity("/application/beans", String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } @@ -116,8 +116,8 @@ public class SampleMethodSecurityApplicationTests { "admin", "admin"); this.restTemplate.getRestTemplate().getInterceptors().add(basicAuthInterceptor); try { - ResponseEntity entity = this.restTemplate.getForEntity("/application/beans", - String.class); + ResponseEntity entity = this.restTemplate + .getForEntity("/application/beans", String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); } finally { @@ -126,22 +126,6 @@ public class SampleMethodSecurityApplicationTests { } } - @Test - public void testManagementUnauthorizedAccess() throws Exception { - BasicAuthorizationInterceptor basicAuthInterceptor = new BasicAuthorizationInterceptor( - "user", "user"); - this.restTemplate.getRestTemplate().getInterceptors().add(basicAuthInterceptor); - try { - ResponseEntity entity = this.restTemplate.getForEntity("/application/beans", - String.class); - assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - } - finally { - this.restTemplate.getRestTemplate().getInterceptors() - .remove(basicAuthInterceptor); - } - } - private void getCsrf(MultiValueMap form, HttpHeaders headers) { ResponseEntity page = this.restTemplate.getForEntity("/login", String.class); diff --git a/spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java b/spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java index 77065dd8c1..59af550ff4 100644 --- a/spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java +++ b/spring-boot/src/main/java/org/springframework/boot/endpoint/web/mvc/WebEndpointServletHandlerMapping.java @@ -106,6 +106,14 @@ public class WebEndpointServletHandlerMapping extends RequestMappingInfoHandlerM setOrder(-100); } + public Collection> getEndpoints() { + return this.webEndpoints; + } + + public String getEndpointPath() { + return this.endpointPath; + } + @Override protected void initHandlerMethods() { this.webEndpoints.stream()