/* * 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.ArrayList; import java.util.Collections; import java.util.List; import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.Valve; import org.apache.catalina.valves.AccessLogValve; 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.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.SearchStrategy; import org.springframework.boot.autoconfigure.hateoas.HypermediaHttpMessageConverterConfiguration; 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.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; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerMapping; 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. * * @author Dave Syer * @author Stephane Nicoll * @author Andy Wilkinson * @author EddĂș MelĂ©ndez * @see EndpointWebMvcAutoConfiguration */ @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(); } @Bean public ServerFactoryCustomization serverCustomization() { return new ServerFactoryCustomization(); } @Bean public UndertowAccessLogCustomizer undertowAccessLogCustomizer() { return new UndertowAccessLogCustomizer(); } @Bean @ConditionalOnClass(name = "org.apache.catalina.valves.AccessLogValve") public TomcatAccessLogCustomizer tomcatAccessLogCustomizer() { 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); } /** * Configuration to add {@link HandlerMapping} for {@link MvcEndpoint}s. */ @Configuration protected static class EndpointHandlerMappingConfiguration { @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); } } @Configuration @ConditionalOnClass({ EnableWebSecurity.class, Filter.class }) @ConditionalOnBean(name = "springSecurityFilterChain", search = SearchStrategy.ANCESTORS) public static class EndpointWebMvcChildContextSecurityConfiguration { @Bean public Filter springSecurityFilterChain(HierarchicalBeanFactory beanFactory) { BeanFactory parent = beanFactory.getParentBeanFactory(); return parent.getBean("springSecurityFilterChain", Filter.class); } } @Configuration @ConditionalOnClass({ LinkDiscoverer.class }) @Import(HypermediaHttpMessageConverterConfiguration.class) @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) static class HypermediaConfiguration { } static class ServerFactoryCustomization implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered { @Autowired private ListableBeanFactory beanFactory; // This needs to be lazily initialized because web server customizer // instances get their callback very early in the context lifecycle. private ManagementServerProperties managementServerProperties; private ServerProperties server; private DefaultServletWebServerFactoryCustomizer serverCustomizer; @Override public int getOrder() { return 0; } @Override public void customize(ConfigurableServletWebServerFactory webServerFactory) { if (this.managementServerProperties == null) { this.managementServerProperties = BeanFactoryUtils .beanOfTypeIncludingAncestors(this.beanFactory, ManagementServerProperties.class); this.server = BeanFactoryUtils.beanOfTypeIncludingAncestors( this.beanFactory, ServerProperties.class); this.serverCustomizer = BeanFactoryUtils.beanOfTypeIncludingAncestors( this.beanFactory, DefaultServletWebServerFactoryCustomizer.class); } // Customize as per the parent context first (so e.g. the access logs go to // the same place) this.serverCustomizer.customize(webServerFactory); // Then reset the error pages webServerFactory.setErrorPages(Collections.<ErrorPage>emptySet()); // and the context path 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())); } } static class CompositeHandlerMapping implements HandlerMapping { @Autowired private ListableBeanFactory beanFactory; private List<HandlerMapping> mappings; @Override public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.mappings == null) { this.mappings = extractMappings(); } for (HandlerMapping mapping : this.mappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } return null; } private List<HandlerMapping> extractMappings() { List<HandlerMapping> list = new ArrayList<>(); list.addAll(this.beanFactory.getBeansOfType(HandlerMapping.class).values()); list.remove(this); AnnotationAwareOrderComparator.sort(list); return list; } } static class CompositeHandlerAdapter implements HandlerAdapter { @Autowired private ListableBeanFactory beanFactory; private List<HandlerAdapter> adapters; private List<HandlerAdapter> extractAdapters() { List<HandlerAdapter> list = new ArrayList<>(); list.addAll(this.beanFactory.getBeansOfType(HandlerAdapter.class).values()); list.remove(this); AnnotationAwareOrderComparator.sort(list); return list; } @Override public boolean supports(Object handler) { if (this.adapters == null) { this.adapters = extractAdapters(); } for (HandlerAdapter mapping : this.adapters) { if (mapping.supports(handler)) { return true; } } return false; } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (this.adapters == null) { this.adapters = extractAdapters(); } for (HandlerAdapter mapping : this.adapters) { if (mapping.supports(handler)) { return mapping.handle(request, response, handler); } } return null; } @Override public long getLastModified(HttpServletRequest request, Object handler) { if (this.adapters == null) { this.adapters = extractAdapters(); } for (HandlerAdapter mapping : this.adapters) { if (mapping.supports(handler)) { return mapping.getLastModified(request, handler); } } return 0; } } static class CompositeHandlerExceptionResolver implements HandlerExceptionResolver { @Autowired private ListableBeanFactory beanFactory; private List<HandlerExceptionResolver> resolvers; private List<HandlerExceptionResolver> extractResolvers() { List<HandlerExceptionResolver> list = new ArrayList<>(); list.addAll(this.beanFactory.getBeansOfType(HandlerExceptionResolver.class) .values()); list.remove(this); AnnotationAwareOrderComparator.sort(list); return list; } @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { if (this.resolvers == null) { this.resolvers = extractResolvers(); } for (HandlerExceptionResolver mapping : this.resolvers) { ModelAndView mav = mapping.resolveException(request, response, handler, ex); if (mav != null) { return mav; } } return null; } } static abstract class AccessLogCustomizer implements Ordered { protected String customizePrefix(String prefix) { return "management_" + prefix; } @Override public int getOrder() { return 1; } } static class TomcatAccessLogCustomizer extends AccessLogCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> { @Override public void customize(TomcatServletWebServerFactory serverFactory) { AccessLogValve accessLogValve = findAccessLogValve(serverFactory); if (accessLogValve == null) { return; } accessLogValve.setPrefix(customizePrefix(accessLogValve.getPrefix())); } private AccessLogValve findAccessLogValve( TomcatServletWebServerFactory serverFactory) { for (Valve engineValve : serverFactory.getEngineValves()) { if (engineValve instanceof AccessLogValve) { return (AccessLogValve) engineValve; } } return null; } } static class UndertowAccessLogCustomizer extends AccessLogCustomizer implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> { @Override public void customize(UndertowServletWebServerFactory serverFactory) { serverFactory.setAccessLogPrefix( customizePrefix(serverFactory.getAccessLogPrefix())); } } }