From fa507005cdf5981499a85ebcd83678d697094cea Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 19 Dec 2013 14:27:36 +0000 Subject: [PATCH] Use ServletWrappingController for jolokia instead of Servlet We get more control over the handling and in particular the registration of the endpoint this way. It was practically impossible to disable the AgentServlet bean when in a parent context of the management server because of lifecyce issues - you don't know that the user wants a separate management server until too late. This approach also makes it possible to test with spring-test MVC support. --- .../EndpointWebMvcAutoConfiguration.java | 52 +++++++------ .../JolokiaAutoConfiguration.java | 37 +++------- .../endpoint/mvc/JolokiaMvcEndpoint.java | 73 ++++++++++++++++++- .../main/resources/META-INF/spring.factories | 2 +- .../JolokiaAutoConfigurationTests.java | 33 +-------- .../mvc/EnvironmentMvcEndpointTests.java | 4 +- .../endpoint/mvc/JolokiaEndpointTests.java | 60 ++++++++++++--- ...rrorControllerSpecialIntegrationTests.java | 1 - .../web/MultipartAutoConfigurationTests.java | 3 +- .../spring-boot-sample-actuator-ui/pom.xml | 4 + .../bind/PropertiesConfigurationFactory.java | 17 +++-- .../bind/PropertySourcesPropertyValues.java | 37 ++++++++-- .../embedded/ServletRegistrationBean.java | 2 +- .../PropertySourcesPropertyValuesTests.java | 21 ++++++ .../test/SpringApplicationContextLoader.java | 3 +- 15 files changed, 240 insertions(+), 109 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java index fb381ea0064..5ce278f5919 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java @@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.Endpoint; @@ -85,8 +86,14 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, private ApplicationContext applicationContext; - @Autowired(required = false) - private ManagementServerProperties managementServerProperties = new ManagementServerProperties(); + @Autowired + private ManagementServerProperties managementServerProperties; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } @Bean @ConditionalOnMissingBean @@ -98,12 +105,6 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, return mapping; } - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.applicationContext) { @@ -114,18 +115,23 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, } } - @Bean - public Filter applicationContextIdFilter(ApplicationContext context) { - final String id = context.getId(); - return new OncePerRequestFilter() { - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - response.addHeader("X-Application-Context", id); - filterChain.doFilter(request, response); - } - }; + // Put Servlets and Filters in their own nested class so they don't force early + // instantiation of ManagementServerProperties. + @Configuration + protected static class ApplicationContextFilterConfiguration { + @Bean + public Filter applicationContextIdFilter(ApplicationContext context) { + final String id = context.getId(); + return new OncePerRequestFilter() { + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + response.addHeader("X-Application-Context", id); + filterChain.doFilter(request, response); + } + }; + } } @Bean @@ -181,11 +187,11 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, childContext.refresh(); } - private enum ManagementServerPort { + protected static enum ManagementServerPort { DISABLE, SAME, DIFFERENT; - public static ManagementServerPort get(ApplicationContext beanFactory) { + public static ManagementServerPort get(BeanFactory beanFactory) { ServerProperties serverProperties; try { @@ -208,7 +214,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, return DISABLE; } if (!(beanFactory instanceof WebApplicationContext)) { - // Current context is no a a webapp + // Current context is not a webapp return DIFFERENT; } return managementServerProperties.getPort() == null diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfiguration.java index 9e4e5eb6cc4..d00e28df68e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfiguration.java @@ -17,23 +17,20 @@ package org.springframework.boot.actuate.autoconfigure; import java.util.Map; +import java.util.Properties; import org.jolokia.http.AgentServlet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpoint; -import org.springframework.boot.actuate.properties.ManagementServerProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; 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.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.bind.RelaxedPropertyResolver; -import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; -import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -54,11 +51,11 @@ import org.springframework.core.env.Environment; * supported configuration parameters. * * @author Christian Dupuis + * @author Dave Syer */ @Configuration @ConditionalOnWebApplication @ConditionalOnClass({ AgentServlet.class }) -@ConditionalOnBean(EmbeddedServletContainerFactory.class) @AutoConfigureBefore(ManagementSecurityAutoConfiguration.class) @AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class) @ConditionalOnExpression("${endpoints.jolokia.enabled:true}") @@ -66,40 +63,28 @@ public class JolokiaAutoConfiguration { private RelaxedPropertyResolver environment; - @Autowired - private ManagementServerProperties management; - @Autowired public void setEnvironment(Environment environment) { this.environment = new RelaxedPropertyResolver(environment); } - @Bean - @ConditionalOnMissingBean({ AgentServlet.class }) - public AgentServlet jolokiaServlet() { - return new AgentServlet(); - } - - @Bean - public ServletRegistrationBean jolokiaServletRegistration(AgentServlet servlet) { - ServletRegistrationBean registrationBean = new ServletRegistrationBean(servlet, - this.management.getContextPath() + jolokiaEndpoint().getPath() + "/*"); - addInitParameters(registrationBean); - return registrationBean; - } - @Bean @ConditionalOnMissingBean public JolokiaMvcEndpoint jolokiaEndpoint() { - return new JolokiaMvcEndpoint(); + JolokiaMvcEndpoint endpoint = new JolokiaMvcEndpoint(); + endpoint.setInitParameters(getInitParameters()); + return endpoint; } - protected void addInitParameters(ServletRegistrationBean registrationBean) { + private Properties getInitParameters() { + Properties properties = new Properties(); Map configParameters = this.environment .getSubProperties("jolokia.config."); for (Map.Entry configParameter : configParameters.entrySet()) { - registrationBean.addInitParameter(configParameter.getKey(), configParameter - .getValue().toString()); + properties.setProperty(configParameter.getKey(), configParameter.getValue() + .toString()); } + return properties; } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java index 1d0473cdcb4..b8564a4cefc 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java @@ -16,11 +16,26 @@ package org.springframework.boot.actuate.endpoint.mvc; +import java.util.Properties; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import org.jolokia.http.AgentServlet; +import org.springframework.beans.BeansException; +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.web.bind.annotation.RequestMapping; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.ServletWrappingController; /** * {@link Endpoint} implementation to register the Jolokia infrastructure with the Boot @@ -29,7 +44,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Christian Dupuis */ @ConfigurationProperties(name = "endpoints.jolokia", ignoreUnknownFields = false) -public class JolokiaMvcEndpoint implements MvcEndpoint { +public class JolokiaMvcEndpoint implements MvcEndpoint, InitializingBean, + ApplicationContextAware, ServletContextAware { @NotNull @Pattern(regexp = "/[^/]*", message = "Path must start with /") @@ -39,8 +55,30 @@ public class JolokiaMvcEndpoint implements MvcEndpoint { private boolean enabled = true; + private ServletWrappingController controller = new ServletWrappingController(); + public JolokiaMvcEndpoint() { this.path = "/jolokia"; + this.controller.setServletClass(AgentServlet.class); + this.controller.setServletName("jolokia"); + } + + @Override + public void afterPropertiesSet() throws Exception { + this.controller.afterPropertiesSet(); + } + + public void setServletContext(ServletContext servletContext) { + this.controller.setServletContext(servletContext); + } + + public void setInitParameters(Properties initParameters) { + this.controller.setInitParameters(initParameters); + } + + public final void setApplicationContext(ApplicationContext context) + throws BeansException { + this.controller.setApplicationContext(context); } public boolean isEnabled() { @@ -74,4 +112,37 @@ public class JolokiaMvcEndpoint implements MvcEndpoint { return null; } + @RequestMapping("/**") + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) + throws Exception { + return this.controller.handleRequest(new PathStripper(request, getPath()), + response); + } + + private static class PathStripper extends HttpServletRequestWrapper { + + private String path; + + public PathStripper(HttpServletRequest request, String path) { + super(request); + this.path = path; + } + + @Override + public String getPathInfo() { + String value = super.getRequestURI(); + if (value.startsWith(this.path)) { + value = value.substring(this.path.length()); + } + int index = value.indexOf("?"); + if (index > 0) { + value = value.substring(0, index); + } + while (value.startsWith("/")) { + value = value.substring(1); + } + return value; + } + + } } 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 9993bce5f2a..524341d7e0f 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -4,8 +4,8 @@ org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.EndpointMBeanExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.ErrorMvcAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.ErrorMvcAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\ diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfigurationTests.java index de66bf45d46..53d37779cb0 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfigurationTests.java @@ -16,26 +16,21 @@ package org.springframework.boot.actuate.autoconfigure; -import javax.servlet.Servlet; -import javax.servlet.ServletRegistration; - -import org.jolokia.http.AgentServlet; import org.junit.After; import org.junit.Test; import org.springframework.boot.TestUtils; +import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpoint; import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory; -import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory.RegisteredServlet; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; /** * Tests for {@link JolokiaAutoConfiguration}. @@ -64,7 +59,7 @@ public class JolokiaAutoConfigurationTests { HttpMessageConvertersAutoConfiguration.class, JolokiaAutoConfiguration.class); this.context.refresh(); - assertEquals(1, this.context.getBeanNamesForType(AgentServlet.class).length); + assertEquals(1, this.context.getBeanNamesForType(JolokiaMvcEndpoint.class).length); } @Test @@ -76,29 +71,7 @@ public class JolokiaAutoConfigurationTests { HttpMessageConvertersAutoConfiguration.class, JolokiaAutoConfiguration.class); this.context.refresh(); - assertEquals(0, this.context.getBeanNamesForType(AgentServlet.class).length); - } - - @Test - public void agentServletRegisteredWithServletContainer() throws Exception { - this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - this.context.register(Config.class, WebMvcAutoConfiguration.class, - ManagementServerPropertiesAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - JolokiaAutoConfiguration.class); - this.context.refresh(); - - Servlet servlet = null; - ServletRegistration.Dynamic registration = null; - for (RegisteredServlet registeredServlet : Config.containerFactory.getContainer() - .getRegisteredServlets()) { - if (registeredServlet.getServlet() instanceof AgentServlet) { - servlet = registeredServlet.getServlet(); - registration = registeredServlet.getRegistration(); - } - } - assertNotNull(servlet); - assertNotNull(registration); + assertEquals(0, this.context.getBeanNamesForType(JolokiaMvcEndpoint.class).length); } @Configuration 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 index 79a9f4cd6e2..b60824dbe57 100644 --- 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 @@ -22,6 +22,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.TestUtils; import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpointTests.TestConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration; @@ -73,7 +74,8 @@ public class EnvironmentMvcEndpointTests { .andExpect(content().string(equalToIgnoringCase("bar"))); } - @Import(EndpointWebMvcAutoConfiguration.class) + @Import({ EndpointWebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) @EnableWebMvc @Configuration public static class TestConfiguration { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaEndpointTests.java index 318a83adb70..fc09993edf0 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaEndpointTests.java @@ -16,21 +16,35 @@ package org.springframework.boot.actuate.endpoint.mvc; +import java.util.Set; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.TestUtils; import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; import org.springframework.boot.actuate.endpoint.mvc.JolokiaEndpointTests.Config; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.context.annotation.Bean; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +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; /** * @author Christian Dupuis @@ -44,21 +58,49 @@ public class JolokiaEndpointTests { @Autowired private MvcEndpoints endpoints; + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Before + public void setUp() { + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + TestUtils.addEnviroment((ConfigurableApplicationContext) this.context, "foo:bar"); + } + @Test public void endpointRegistered() throws Exception { - assertEquals(1, this.endpoints.getEndpoints().size()); + Set values = this.endpoints.getEndpoints(); + assertEquals(1, values.size()); + assertTrue(values.iterator().next() instanceof JolokiaMvcEndpoint); + } + + @Test + public void search() throws Exception { + this.mvc.perform(get("/jolokia/search/java.lang:*")).andExpect(status().isOk()) + .andExpect(content().string(containsString("GarbageCollector"))); + } + + @Test + public void read() throws Exception { + this.mvc.perform(get("/jolokia/read/java.lang:type=Memory")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("NonHeapMemoryUsage"))); + } + + @Test + public void list() throws Exception { + this.mvc.perform(get("/jolokia/list/java.lang/type=Memory/attr")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("NonHeapMemoryUsage"))); } @Configuration @EnableConfigurationProperties @EnableWebMvc - @Import(EndpointWebMvcAutoConfiguration.class) + @Import({ EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) public static class Config { - - @Bean - public JolokiaMvcEndpoint endpoint() { - return new JolokiaMvcEndpoint(); - } - } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/BasicErrorControllerSpecialIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/BasicErrorControllerSpecialIntegrationTests.java index efbc69ff7f4..5e358c6751b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/BasicErrorControllerSpecialIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/BasicErrorControllerSpecialIntegrationTests.java @@ -112,7 +112,6 @@ public class BasicErrorControllerSpecialIntegrationTests { @EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class, ManagementSecurityAutoConfiguration.class }) protected static class ChildConfiguration { - // For manual testing public static void main(String[] args) { new SpringApplicationBuilder(ParentConfiguration.class).child( diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java index dbb1ded3d3b..018d9d4935c 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java @@ -139,12 +139,13 @@ public class MultipartAutoConfigurationTests { } @Test - public void containerWithAutomatedMultipartTomcatConfiguration() { + public void containerWithAutomatedMultipartTomcatConfiguration() throws Exception { this.context = new AnnotationConfigEmbeddedWebApplicationContext( ContainerWithEverythingTomcat.class, EmbeddedServletContainerAutoConfiguration.class, DispatcherServletAutoConfiguration.class, MultipartAutoConfiguration.class); + new RestTemplate().getForObject("http://localhost:8080/", String.class); this.context.getBean(MultipartConfigElement.class); assertSame(this.context.getBean(DispatcherServlet.class).getMultipartResolver(), this.context.getBean(StandardServletMultipartResolver.class)); diff --git a/spring-boot-samples/spring-boot-sample-actuator-ui/pom.xml b/spring-boot-samples/spring-boot-sample-actuator-ui/pom.xml index 805f4a2c2f2..3f66493a44e 100644 --- a/spring-boot-samples/spring-boot-sample-actuator-ui/pom.xml +++ b/spring-boot-samples/spring-boot-sample-actuator-ui/pom.xml @@ -36,6 +36,10 @@ org.thymeleaf thymeleaf-spring4 + + org.jolokia + jolokia-core + diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java index 6551b9bdb8d..8ccf9ac5b56 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java @@ -249,23 +249,30 @@ public class PropertiesConfigurationFactory implements FactoryBean, customizeBinder(dataBinder); Set names = new HashSet(); + Set patterns = new HashSet(); if (this.target != null) { PropertyDescriptor[] descriptors = BeanUtils .getPropertyDescriptors(this.target.getClass()); + String[] prefixes = this.targetName != null ? new String[] { + this.targetName + ".", this.targetName + "_" } : new String[] { "" }; + String[] suffixes = new String[] { ".*", "_*" }; for (PropertyDescriptor descriptor : descriptors) { String name = descriptor.getName(); if (!name.equals("class")) { - names.add(name); - names.add(name + ".*"); - names.add(name + "_*"); - names.add("*_"+name); + for (String prefix : prefixes) { + names.add(prefix + name); + patterns.add(prefix + name); + for (String suffix : suffixes) { + patterns.add(prefix + name + suffix); + } + } } } } PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues( this.properties) : new PropertySourcesPropertyValues( - this.propertySources, names)); + this.propertySources, patterns, names)); dataBinder.bind(propertyValues); if (this.validator != null) { diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java index c30daa9fcc3..618fead9a40 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java @@ -45,37 +45,39 @@ public class PropertySourcesPropertyValues implements PropertyValues { private PropertySources propertySources; - private Collection NON_ENUMERABLES = Arrays.asList( + private Collection NON_ENUMERABLE_ENUMERABLES = Arrays.asList( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, - StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);; + StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME); /** * Create a new PropertyValues from the given PropertySources * @param propertySources a PropertySources instance */ public PropertySourcesPropertyValues(PropertySources propertySources) { - this(propertySources, null); + this(propertySources, null, null); } /** * Create a new PropertyValues from the given PropertySources * @param propertySources a PropertySources instance - * @param systemPropertyNames property names to include from system properties and + * @param patterns property name patterns to include from system properties and * environment variables + * @param names exact property names to include */ public PropertySourcesPropertyValues(PropertySources propertySources, - Collection systemPropertyNames) { + Collection patterns, Collection names) { this.propertySources = propertySources; PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver( propertySources); - String[] includes = systemPropertyNames == null ? new String[0] - : systemPropertyNames.toArray(new String[0]); + String[] includes = patterns == null ? new String[0] : patterns + .toArray(new String[0]); + String[] exacts = names == null ? new String[0] : names.toArray(new String[0]); for (PropertySource source : propertySources) { if (source instanceof EnumerablePropertySource) { EnumerablePropertySource enumerable = (EnumerablePropertySource) source; if (enumerable.getPropertyNames().length > 0) { for (String propertyName : enumerable.getPropertyNames()) { - if (this.NON_ENUMERABLES.contains(source.getName()) + if (this.NON_ENUMERABLE_ENUMERABLES.contains(source.getName()) && !PatternMatchUtils.simpleMatch(includes, propertyName)) { continue; } @@ -91,6 +93,25 @@ public class PropertySourcesPropertyValues implements PropertyValues { } } } + else { + // We can only do exact matches for non-enumerable property names, but + // that's better than nothing... + for (String propertyName : exacts) { + Object value; + value = source.getProperty(propertyName); + if (value != null) { + this.propertyValues.put(propertyName, new PropertyValue( + propertyName, value)); + continue; + } + value = source.getProperty(propertyName.toUpperCase()); + if (value != null) { + this.propertyValues.put(propertyName, new PropertyValue( + propertyName, value)); + continue; + } + } + } } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ServletRegistrationBean.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ServletRegistrationBean.java index e99c7b86f89..38d92062273 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ServletRegistrationBean.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ServletRegistrationBean.java @@ -52,7 +52,7 @@ public class ServletRegistrationBean extends RegistrationBean { private Set urlMappings = new LinkedHashSet(); - private int loadOnStartup = 1; + private int loadOnStartup = -1; private MultipartConfigElement multipartConfig; diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java index c8bb7153f59..c8a1273c237 100644 --- a/spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java @@ -90,6 +90,15 @@ public class PropertySourcesPropertyValuesTests { assertEquals("bar", target.getName()); } + @Test + public void testPlaceholdersBindingNonEnumerable() { + FooBean target = new FooBean(); + DataBinder binder = new DataBinder(target); + binder.bind(new PropertySourcesPropertyValues(this.propertySources, null, + Collections.singleton("foo"))); + assertEquals("bar", target.getFoo()); + } + @Test public void testPlaceholdersBindingWithError() { TestBean target = new TestBean(); @@ -112,4 +121,16 @@ public class PropertySourcesPropertyValuesTests { } } + public static class FooBean { + private String foo; + + public String getFoo() { + return this.foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + } + } diff --git a/spring-boot/src/test/java/org/springframework/boot/test/SpringApplicationContextLoader.java b/spring-boot/src/test/java/org/springframework/boot/test/SpringApplicationContextLoader.java index 9160ce82c6f..4653c14500a 100644 --- a/spring-boot/src/test/java/org/springframework/boot/test/SpringApplicationContextLoader.java +++ b/spring-boot/src/test/java/org/springframework/boot/test/SpringApplicationContextLoader.java @@ -88,8 +88,7 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { private Map getArgs(MergedContextConfiguration mergedConfig) { Map args = new LinkedHashMap(); // Not running an embedded server, just setting up web context - args.put("server.port", "0"); - args.put("management.port", "0"); + args.put("server.port", "-1"); return args; }