diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java index 628f3461540..42766b72347 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.validation.MessageInterpolatorFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; @@ -51,7 +52,10 @@ public class ValidationAutoConfiguration { @Bean @ConditionalOnMissingBean public Validator validator() { - return new LocalValidatorFactoryBean(); + LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); + MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(); + factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); + return factoryBean; } @Bean diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 926eac6a86f..02e972c0de6 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -87,7 +87,7 @@ 3.7.1 1.1.3 5.0.11.Final - 5.2.4.Final + 5.3.4.Final 2.5.1 2.3.13 2.3.3 diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index 9e711c4d895..3bf2c3d01e8 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -35,6 +35,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.bind.PropertiesConfigurationFactory; +import org.springframework.boot.validation.MessageInterpolatorFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; @@ -400,7 +401,9 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc public Validator run(ApplicationContext applicationContext) { LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(); validator.setApplicationContext(applicationContext); + validator.setMessageInterpolator(interpolatorFactory.getObject()); validator.afterPropertiesSet(); return validator; } diff --git a/spring-boot/src/main/java/org/springframework/boot/validation/MessageInterpolatorFactory.java b/spring-boot/src/main/java/org/springframework/boot/validation/MessageInterpolatorFactory.java new file mode 100644 index 00000000000..9df5adc1b02 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/validation/MessageInterpolatorFactory.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.validation; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.validation.MessageInterpolator; +import javax.validation.Validation; +import javax.validation.ValidationException; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.util.ClassUtils; + +/** + * {@link ObjectFactory} that can be used to create a {@link MessageInterpolatorFactory}. + * Attempts to pick the most appropriate {@link MessageInterpolator} based on the + * classpath. + * + * @author Phillip Webb + */ +public class MessageInterpolatorFactory implements ObjectFactory { + + private static final Set FALLBACKS; + + static { + Set fallbacks = new LinkedHashSet(); + fallbacks.add("org.hibernate.validator.messageinterpolation" + + ".ParameterMessageInterpolator"); + FALLBACKS = Collections.unmodifiableSet(fallbacks); + } + + @Override + public MessageInterpolator getObject() throws BeansException { + try { + return Validation.byDefaultProvider().configure() + .getDefaultMessageInterpolator(); + } + catch (ValidationException ex) { + MessageInterpolator fallback = getFallback(); + if (fallback != null) { + return fallback; + } + throw ex; + } + } + + private MessageInterpolator getFallback() { + for (String fallback : FALLBACKS) { + try { + return getFallback(fallback); + } + catch (Exception ex) { + // Swallow an continue + } + } + return null; + } + + private MessageInterpolator getFallback(String fallback) { + Class interpolatorClass = ClassUtils.resolveClassName(fallback, null); + Object interpolator = BeanUtils.instantiate(interpolatorClass); + return (MessageInterpolator) interpolator; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/validation/package-info.java b/spring-boot/src/main/java/org/springframework/boot/validation/package-info.java new file mode 100644 index 00000000000..f4729111736 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/validation/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Utilities and classes related to validation. + */ +package org.springframework.boot.validation; diff --git a/spring-boot/src/test/java/org/springframework/boot/validation/MessageInterpolatorFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/validation/MessageInterpolatorFactoryTests.java new file mode 100644 index 00000000000..e7e63f8c2dd --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/validation/MessageInterpolatorFactoryTests.java @@ -0,0 +1,39 @@ +/* + * 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.validation; + +import javax.validation.MessageInterpolator; + +import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MessageInterpolatorFactory}. + * + * @author Phillip Webb + */ +public class MessageInterpolatorFactoryTests { + + @Test + public void getObjectShouldReturnResourceBundleMessageInterpolator() { + MessageInterpolator interpolator = new MessageInterpolatorFactory().getObject(); + assertThat(interpolator).isInstanceOf(ResourceBundleMessageInterpolator.class); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/validation/MessageInterpolatorFactoryWithoutElIntegrationTests.java b/spring-boot/src/test/java/org/springframework/boot/validation/MessageInterpolatorFactoryWithoutElIntegrationTests.java new file mode 100644 index 00000000000..e8fb801fb13 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/validation/MessageInterpolatorFactoryWithoutElIntegrationTests.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.validation; + +import javax.validation.MessageInterpolator; +import javax.validation.Validation; +import javax.validation.ValidationException; + +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import org.springframework.boot.junit.runner.classpath.ClassPathExclusions; +import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link MessageInterpolatorFactory} without EL. + * + * @author Phillip Webb + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathExclusions("tomcat-embed-el-*.jar") +public class MessageInterpolatorFactoryWithoutElIntegrationTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void defaultMessageInterpolatorShouldFail() throws Exception { + // Sanity test + this.thrown.expect(ValidationException.class); + this.thrown.expectMessage("javax.el.ExpressionFactory"); + Validation.byDefaultProvider().configure().getDefaultMessageInterpolator(); + } + + @Test + public void getObjectShouldUseFallback() { + MessageInterpolator interpolator = new MessageInterpolatorFactory().getObject(); + assertThat(interpolator).isInstanceOf(ParameterMessageInterpolator.class); + } + +}