From 0b50fe4eff29fdc0ec025281703c7bb3b9e22705 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 24 Sep 2014 16:03:51 -0700 Subject: [PATCH 1/4] Support String to char[] bindings Update RelaxedConversionService to also support String to char[] conversion. Primarily to support the `password` field in MongoProperties. Fixes gh-1572 --- .../mongo/MongoPropertiesTests.java | 52 +++++++++++++++++++ .../boot/bind/RelaxedConversionService.java | 1 + .../boot/bind/StringToCharArrayConverter.java | 33 ++++++++++++ ...onPropertiesBindingPostProcessorTests.java | 27 ++++++++++ 4 files changed, 113 insertions(+) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/bind/StringToCharArrayConverter.java diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java new file mode 100644 index 0000000000..60105a2a52 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoPropertiesTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2014 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.autoconfigure.mongo; + +import org.junit.Test; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link MongoProperties}. + * + * @author Phillip Webb + */ +public class MongoPropertiesTests { + + @Test + public void canBindCharArrayPassword() { + // gh-1572 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(context, "spring.data.mongodb.password:word"); + context.register(Conf.class); + context.refresh(); + MongoProperties properties = context.getBean(MongoProperties.class); + assertThat(properties.getPassword(), equalTo("word".toCharArray())); + } + + @Configuration + @EnableConfigurationProperties(MongoProperties.class) + static class Conf { + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedConversionService.java b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedConversionService.java index 626f704a07..a645e94cb8 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedConversionService.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedConversionService.java @@ -49,6 +49,7 @@ class RelaxedConversionService implements ConversionService { this.additionalConverters = new GenericConversionService(); this.additionalConverters .addConverterFactory(new StringToEnumIgnoringCaseConverterFactory()); + this.additionalConverters.addConverter(new StringToCharArrayConverter()); } @Override diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/StringToCharArrayConverter.java b/spring-boot/src/main/java/org/springframework/boot/bind/StringToCharArrayConverter.java new file mode 100644 index 0000000000..1c3e20bcbb --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/bind/StringToCharArrayConverter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2014 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.bind; + +import org.springframework.core.convert.converter.Converter; + +/** + * Converts a String to a Char Array. + * + * @author Phillip Webb + */ +class StringToCharArrayConverter implements Converter { + + @Override + public char[] convert(String source) { + return source.toCharArray(); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java index bf066fbebf..586c99e4d0 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java @@ -168,6 +168,16 @@ public class ConfigurationPropertiesBindingPostProcessorTests { assertTrue("No init", ConfigurationPropertiesWithFactoryBean.factoryBeanInit); } + @Test + public void configurationPropertiesWithCharArray() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "test.chars:word"); + this.context.register(PropertyWithCharArray.class); + this.context.refresh(); + assertThat(this.context.getBean(PropertyWithCharArray.class).getChars(), + equalTo("word".toCharArray())); + } + @Configuration @EnableConfigurationProperties public static class TestConfigurationWithValidatingSetter { @@ -282,6 +292,23 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } + @Configuration + @EnableConfigurationProperties + @ConfigurationProperties(prefix = "test") + public static class PropertyWithCharArray { + + private char[] chars; + + public char[] getChars() { + return this.chars; + } + + public void setChars(char[] chars) { + this.chars = chars; + } + + } + @Configuration @EnableConfigurationProperties @ConfigurationProperties(prefix = "test") From 68ff7d4592a1ac9291a4b5528199e01d84b73362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20DERACO?= Date: Wed, 24 Sep 2014 23:27:41 +0200 Subject: [PATCH 2/4] Fix broken documentation links Fix links to `actuator-noweb`, `actuator-log4j` and `hornetq` samples. Fixes gh-1613 --- spring-boot-samples/README.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-boot-samples/README.adoc b/spring-boot-samples/README.adoc index 6757696dd7..e675a89fae 100644 --- a/spring-boot-samples/README.adoc +++ b/spring-boot-samples/README.adoc @@ -10,9 +10,9 @@ -- Simple REST service with production features * link:spring-boot-sample-actuator-ui[spring-boot-sample-actuator-ui] -- A web UI example with production features -* link:spring-boot-sample-actuator-ui[spring-boot-sample-actuator-noweb] +* link:spring-boot-sample-actuator-noweb[spring-boot-sample-actuator-noweb] -- A production features sample with no web application -* link:spring-boot-sample-actuator-ui[spring-boot-sample-actuator-log4j] +* link:spring-boot-sample-actuator-log4j[spring-boot-sample-actuator-log4j] -- A production features sample using log4j for logging (instead of logback) * link:spring-boot-sample-web-ui[spring-boot-sample-web-ui] -- A thymeleaf web application @@ -48,7 +48,7 @@ -- Example showing database migrations with Liquibase * link:spring-boot-sample-amqp[spring-boot-sample-amqp] -- Example showing message-oriented application using RabbitMQ -* link:spring-boot-sample-amqp[spring-boot-sample-hornetq] +* link:spring-boot-sample-hornetq[spring-boot-sample-hornetq] -- Example showing message-oriented application using HornetQ * link:spring-boot-sample-batch[spring-boot-sample-batch] -- Define and run a Batch job in a few lines of code From 304920df073fd20ae68d77d44a08773b6db7c418 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 25 Sep 2014 13:55:18 +0100 Subject: [PATCH 3/4] Make Thymeleaf @ConditionalOnWebApplication If user creates a Thymeleaf app with a parent-child context then the child should contain all the web-specific pieces (and they are likely to fail fast if they need to be ServletContextAware, or slower if they try to locate a WebApplicationContext at runtime). This can't happen if the view resolver is being added to the parent. Freemarker and Velocity already have similar tests because it is assumed that they should be usable outside a web app, so this change just does the same for Thymeleaf. Fixes gh-1611 --- .../thymeleaf/ThymeleafAutoConfiguration.java | 8 ++-- .../ThymeleafAutoConfigurationTests.java | 45 ++++++++++++++++++- .../BasicErrorControllerIntegrationTest.java | 3 +- .../src/test/resources/templates/home.html | 1 + .../src/test/resources/templates/message.html | 1 + 5 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 spring-boot-autoconfigure/src/test/resources/templates/message.html diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java index 9f077cb0db..18a47d4e89 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java @@ -29,6 +29,7 @@ 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.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.EnvironmentAware; @@ -149,19 +150,20 @@ public class ThymeleafAutoConfiguration { @Configuration @ConditionalOnClass({ Servlet.class }) + @ConditionalOnWebApplication protected static class ThymeleafViewResolverConfiguration implements EnvironmentAware { private RelaxedPropertyResolver environment; + @Autowired + private SpringTemplateEngine templateEngine; + @Override public void setEnvironment(Environment environment) { this.environment = new RelaxedPropertyResolver(environment, "spring.thymeleaf."); } - @Autowired - private SpringTemplateEngine templateEngine; - @Bean @ConditionalOnMissingBean(name = "thymeleafViewResolver") public ThymeleafViewResolver thymeleafViewResolver() { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfigurationTests.java index 89f69be6b2..b28ff65c1a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfigurationTests.java @@ -30,6 +30,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.support.RequestContext; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; @@ -38,8 +39,10 @@ import org.thymeleaf.spring4.view.ThymeleafViewResolver; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.TemplateResolver; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** @@ -49,7 +52,7 @@ import static org.junit.Assert.assertTrue; */ public class ThymeleafAutoConfigurationTests { - private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + private AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); @After public void close() { @@ -137,4 +140,44 @@ public class ThymeleafAutoConfigurationTests { context.close(); } + @Test + public void useDataDialect() throws Exception { + this.context.register(ThymeleafAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + TemplateEngine engine = this.context.getBean(TemplateEngine.class); + Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); + String result = engine.process("data-dialect", attrs); + assertEquals("", result); + } + + @Test + public void renderTemplate() throws Exception { + this.context.register(ThymeleafAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + TemplateEngine engine = this.context.getBean(TemplateEngine.class); + Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); + String result = engine.process("home", attrs); + assertEquals("bar", result); + } + + @Test + public void renderNonWebAppTemplate() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + ThymeleafAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + assertEquals(0, context.getBeanNamesForType(ViewResolver.class).length); + try { + TemplateEngine engine = context.getBean(TemplateEngine.class); + Context attrs = new Context(Locale.UK, Collections.singletonMap("greeting", + "Hello World")); + String result = engine.process("message", attrs); + assertThat(result, containsString("Hello World")); + } + finally { + context.close(); + } + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTest.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTest.java index 991b966b8c..a5ceca8673 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTest.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTest.java @@ -81,7 +81,8 @@ public class BasicErrorControllerIntegrationTest { "http://localhost:" + this.port + "/bind", Map.class); String resp = entity.getBody().toString(); assertThat(resp, containsString("Error count: 1")); - assertThat(resp, containsString("errors=[{codes=")); + assertThat(resp, containsString("errors=[{")); + assertThat(resp, containsString("codes=[")); assertThat(resp, containsString("org.springframework.validation.BindException")); } diff --git a/spring-boot-autoconfigure/src/test/resources/templates/home.html b/spring-boot-autoconfigure/src/test/resources/templates/home.html index e69de29bb2..8f364d3623 100644 --- a/spring-boot-autoconfigure/src/test/resources/templates/home.html +++ b/spring-boot-autoconfigure/src/test/resources/templates/home.html @@ -0,0 +1 @@ +Home \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/templates/message.html b/spring-boot-autoconfigure/src/test/resources/templates/message.html new file mode 100644 index 0000000000..53440f08e7 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/templates/message.html @@ -0,0 +1 @@ +Message: Hello \ No newline at end of file From 336b96b81cfcf2ba2eb4b662a7e671e36a225bec Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 25 Sep 2014 17:39:53 +0100 Subject: [PATCH 4/4] Copy server customization to management context If the actuator endpoints are configured on a different port then there are some settings in the main ServerProperties that we would like to re-use (e.g. the access log). The easiest way to do that is to just configure the management server using the same ServerProperties instance and then overwrite the things that are different (and stored in ManagementServerProperties). Fixes gh-1581 --- .../EndpointWebMvcChildContextConfiguration.java | 12 ++++++++++++ .../EndpointWebMvcAutoConfigurationTests.java | 16 +++++++++++++++- .../src/main/resources/application.properties | 1 + 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java index c15421aa15..35db5b3863 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -36,6 +37,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.web.ErrorAttributes; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; +import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; @@ -75,13 +77,23 @@ public class EndpointWebMvcChildContextConfiguration { // instances get their callback very early in the context lifecycle. private ManagementServerProperties managementServerProperties; + private ServerProperties server; + @Override public void customize(ConfigurableEmbeddedServletContainer container) { if (this.managementServerProperties == null) { this.managementServerProperties = BeanFactoryUtils .beanOfTypeIncludingAncestors(this.beanFactory, ManagementServerProperties.class); + this.server = BeanFactoryUtils + .beanOfTypeIncludingAncestors(this.beanFactory, + ServerProperties.class); } + // Customize as per the parent context first (so e.g. the access logs go to the same place) + server.customize(container); + // Then reset the error pages + container.setErrorPages(Collections.emptySet()); + // and add the management-specific bits container.setPort(this.managementServerProperties.getPort()); container.setAddress(this.managementServerProperties.getAddress()); container.setContextPath(this.managementServerProperties.getContextPath()); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 21218792b8..5c1b4e13a4 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -35,6 +35,7 @@ import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; +import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.boot.test.EnvironmentTestUtils; @@ -222,6 +223,7 @@ public class EndpointWebMvcAutoConfigurationTests { assertThat(localServerPort, notNullValue()); assertThat(localManagementPort, notNullValue()); assertThat(localServerPort, not(equalTo(localManagementPort))); + assertThat(applicationContext.getBean(ServerPortConfig.class).getCount(), equalTo(2)); this.applicationContext.close(); assertAllClosed(); } @@ -303,10 +305,22 @@ public class EndpointWebMvcAutoConfigurationTests { @Configuration public static class ServerPortConfig { + + private int count = 0; + + public int getCount() { + return count; + } @Bean public ServerProperties serverProperties() { - ServerProperties properties = new ServerProperties(); + ServerProperties properties = new ServerProperties() { + @Override + public void customize(ConfigurableEmbeddedServletContainer container) { + count++; + super.customize(container); + } + }; properties.setPort(ports.get().server); return properties; } diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-actuator/src/main/resources/application.properties index 49672f79a1..d36c067f87 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-actuator/src/main/resources/application.properties @@ -1,6 +1,7 @@ logging.file: /tmp/logs/app.log logging.level.org.springframework.security: INFO management.address: 127.0.0.1 +#management.port: 8181 endpoints.shutdown.enabled: true server.tomcat.basedir: target/tomcat server.tomcat.access_log_enabled: true