From c9561f031c591ca55e028ca66250511c5e62002a Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 17 Apr 2017 22:32:02 -0700 Subject: [PATCH 1/5] Refine validator and MVC validator configuration Update `ValidationAutoConfiguration` and `WebMvcAutoConfiguration` to ensure as much as possible that only a single Validator bean of each type is registered. Validation auto-configuration now does the following: - If no validator is found: Registers a `LocalValidatorFactoryBean` (providing both Spring and JSR validation) - If the user defines a Spring & JSR validator: Backs off - If the user defines only a JSR validator: Adapts it to a Spring validator (without exposing another JSR implementation) WebMvcAutoConfiguration auto-configuration has been updated to make MVC validation follow common Spring Boot patterns: - If not validator beans are found (due to the user excluding ValidationAutoConfiguration) a new `mvcValidator` bean will be registered. - If a single validator bean is found it will be used for MVC validation. - If multiple validator beans are defined it will either use the one named `mvcValidator` or it will register a new `mvcValidator` bean Any automatically registered `mvcValidator` bean will not implement the JSR validator interface. Finally, it is no longer possible to provide an MVC validator via a `WebMvcConfigurer`. Fixes gh-8495 --- .../EndpointMvcIntegrationTests.java | 7 +- .../DefaultValidatorConfiguration.java | 47 ++++ .../validation/DelegatingValidator.java | 78 ++++++ .../Jsr303ValidatorAdapterConfiguration.java | 46 ++++ .../ValidationAutoConfiguration.java | 19 +- .../web/WebMvcAutoConfiguration.java | 178 +++++++++++- .../autoconfigure/web/WebMvcValidator.java | 144 ---------- ...ringBootWebSecurityConfigurationTests.java | 7 +- .../validation/DelegatingValidatorTests.java | 117 ++++++++ .../ValidationAutoConfigurationTests.java | 160 ++++++++--- ...asicErrorControllerDirectMockMvcTests.java | 7 +- .../web/BasicErrorControllerMockMvcTests.java | 7 +- .../web/WebMvcAutoConfigurationTests.java | 258 +++++++++++++----- .../web/WebMvcValidatorTests.java | 152 ----------- 14 files changed, 785 insertions(+), 442 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DefaultValidatorConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/Jsr303ValidatorAdapterConfiguration.java delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcValidator.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java delete mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcValidatorTests.java diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java index d6312f7dc34..146b99e4b40 100755 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java @@ -41,6 +41,7 @@ import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCusto import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration; @@ -156,9 +157,9 @@ public class EndpointMvcIntegrationTests { @Documented @Import({ EmbeddedServletContainerAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - JacksonAutoConfiguration.class, ErrorMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, + WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class, + ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) protected @interface MinimalWebConfiguration { } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DefaultValidatorConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DefaultValidatorConfiguration.java new file mode 100644 index 00000000000..9311a272c6f --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DefaultValidatorConfiguration.java @@ -0,0 +1,47 @@ +/* + * 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.autoconfigure.validation; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.validation.MessageInterpolatorFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +/** + * Default validator configuration imported by {@link ValidationAutoConfiguration}. + * + * @author Stephane Nicoll + * @author Phillip Webb + */ +@Configuration +class DefaultValidatorConfiguration { + + @Bean + @ConditionalOnMissingBean(type = { "javax.validation.Validator", + "org.springframework.validation.Validator" }) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public static LocalValidatorFactoryBean defaultValidator() { + LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); + MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(); + factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); + return factoryBean; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java new file mode 100644 index 00000000000..dc9fc725b6b --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java @@ -0,0 +1,78 @@ +/* + * 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.autoconfigure.validation; + +import org.springframework.util.Assert; +import org.springframework.validation.Errors; +import org.springframework.validation.SmartValidator; +import org.springframework.validation.Validator; +import org.springframework.validation.beanvalidation.SpringValidatorAdapter; + +/** + * {@link Validator} implementation that delegates calls to another {@link Validator}. + * This {@link Validator} implements Spring's {@link SmartValidator} interface but does + * not implement the JSR-303 {@code javax.validator.Validator} interface. + * + * @author Phillip Webb + * @since 1.5.3 + */ +public class DelegatingValidator implements SmartValidator { + + private final Validator delegate; + + /** + * Create a new {@link DelegatingValidator} instance. + * @param targetValidator the target JSR validator + */ + public DelegatingValidator(javax.validation.Validator targetValidator) { + this.delegate = new SpringValidatorAdapter(targetValidator); + } + + /** + * Create a new {@link DelegatingValidator} instance. + * @param targetValidator the target validator + */ + public DelegatingValidator(Validator targetValidator) { + Assert.notNull(targetValidator, "Target Validator must not be null"); + this.delegate = targetValidator; + } + + @Override + public boolean supports(Class clazz) { + return this.delegate.supports(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + this.delegate.validate(target, errors); + } + + @Override + public void validate(Object target, Errors errors, Object... validationHints) { + if (this.delegate instanceof SmartValidator) { + ((SmartValidator) this.delegate).validate(target, errors, validationHints); + } + else { + this.delegate.validate(target, errors); + } + } + + protected final Validator getDelegate() { + return this.delegate; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/Jsr303ValidatorAdapterConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/Jsr303ValidatorAdapterConfiguration.java new file mode 100644 index 00000000000..2573dd3e3cf --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/Jsr303ValidatorAdapterConfiguration.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.autoconfigure.validation; + +import javax.validation.Validator; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.validation.SmartValidator; + +/** + * JSR 303 adapter configration imported by {@link ValidationAutoConfiguration}. + * + * @author Stephane Nicoll + * @author Phillip Webb + */ +@Configuration +class Jsr303ValidatorAdapterConfiguration { + + @Bean + @ConditionalOnSingleCandidate(Validator.class) + @ConditionalOnMissingBean(org.springframework.validation.Validator.class) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public SmartValidator jsr303ValidatorAdapter(Validator validator) { + return new DelegatingValidator(validator); + } + +} 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 9feba19dad6..99c4efa6b9b 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 @@ -19,18 +19,16 @@ package org.springframework.boot.autoconfigure.validation; import javax.validation.Validator; import javax.validation.executable.ExecutableValidator; -import org.springframework.beans.factory.config.BeanDefinition; 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.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; import org.springframework.boot.bind.RelaxedPropertyResolver; -import org.springframework.boot.validation.MessageInterpolatorFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Role; +import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; /** @@ -43,19 +41,12 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess @Configuration @ConditionalOnClass(ExecutableValidator.class) @ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider") +@Import({ DefaultValidatorConfiguration.class, + Jsr303ValidatorAdapterConfiguration.class }) public class ValidationAutoConfiguration { @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - @ConditionalOnMissingBean - public static Validator jsr303Validator() { - LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); - MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(); - factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); - return factoryBean; - } - - @Bean + @ConditionalOnBean(Validator.class) @ConditionalOnMissingBean public static MethodValidationPostProcessor methodValidationPostProcessor( Environment environment, Validator validator) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index b96fc8b152b..f08693eaca0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -30,11 +30,21 @@ import javax.servlet.http.HttpServletRequest; 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.BeanFactoryUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -43,27 +53,38 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.validation.DelegatingValidator; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.filter.OrderedHiddenHttpMethodFilter; import org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter; import org.springframework.boot.web.filter.OrderedRequestContextFilter; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; 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.annotation.Primary; +import org.springframework.context.annotation.Role; import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.io.Resource; +import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.datetime.DateFormatter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.MessageCodesResolver; @@ -142,6 +163,12 @@ public class WebMvcAutoConfiguration { public static final String SKIP_PATH_EXTENSION_CONTENT_NEGOTIATION_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class .getName() + ".SKIP"; + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public static MvcValidatorPostProcessor mvcValidatorAliasPostProcessor() { + return new MvcValidatorPostProcessor(); + } + @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { @@ -367,21 +394,22 @@ public class WebMvcAutoConfiguration { * Configuration equivalent to {@code @EnableWebMvc}. */ @Configuration - public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { + public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration + implements InitializingBean { private final WebMvcProperties mvcProperties; - private final ListableBeanFactory beanFactory; + private final ApplicationContext context; private final WebMvcRegistrations mvcRegistrations; public EnableWebMvcConfiguration( ObjectProvider mvcPropertiesProvider, ObjectProvider mvcRegistrationsProvider, - ListableBeanFactory beanFactory) { + ApplicationContext context) { this.mvcProperties = mvcPropertiesProvider.getIfAvailable(); this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique(); - this.beanFactory = beanFactory; + this.context = context; } @Bean @@ -412,12 +440,9 @@ public class WebMvcAutoConfiguration { @Bean @Override + @Conditional(DisableMvcValidatorCondition.class) public Validator mvcValidator() { - if (!ClassUtils.isPresent("javax.validation.Validator", - getClass().getClassLoader())) { - return super.mvcValidator(); - } - return WebMvcValidator.get(getApplicationContext(), getValidator()); + return this.context.getBean("mvcValidator", Validator.class); } @Override @@ -432,7 +457,7 @@ public class WebMvcAutoConfiguration { @Override protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() { try { - return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class); + return this.context.getBean(ConfigurableWebBindingInitializer.class); } catch (NoSuchBeanDefinitionException ex) { return super.getConfigurableWebBindingInitializer(); @@ -481,6 +506,15 @@ public class WebMvcAutoConfiguration { return manager; } + @Override + public void afterPropertiesSet() throws Exception { + Assert.state(getValidator() == null, + "Found unexpected validator configuration. A Spring Boot MVC " + + "validator should be registered as bean named " + + "'mvcValidator' and not returned from " + + "WebMvcConfigurer.getValidator()"); + } + } @Configuration @@ -606,4 +640,128 @@ public class WebMvcAutoConfiguration { } + /** + * Condition used to disable the default MVC validator registration. The + * {@link MvcValidatorPostProcessor} is used to configure the {@code mvcValidator} + * bean. + */ + static class DisableMvcValidatorCondition implements ConfigurationCondition { + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.REGISTER_BEAN; + } + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return false; + } + + } + + /** + * {@link BeanFactoryPostProcessor} to deal with the MVC validator bean registration. + * Applies the following rules: + *
    + *
  • With no validators - Uses standard + * {@link WebMvcConfigurationSupport#mvcValidator()} logic.
  • + *
  • With a single validator - Uses an alias.
  • + *
  • With multiple validators - Registers a mvcValidator bean if not already + * defined.
  • + *
+ */ + @Order(Ordered.LOWEST_PRECEDENCE) + static class MvcValidatorPostProcessor + implements BeanDefinitionRegistryPostProcessor { + + private static final String JSR303_VALIDATOR_CLASS = "javax.validation.Validator"; + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) + throws BeansException { + if (registry instanceof ListableBeanFactory) { + postProcess(registry, (ListableBeanFactory) registry); + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + } + + private void postProcess(BeanDefinitionRegistry registry, + ListableBeanFactory beanFactory) { + String[] validatorBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + beanFactory, Validator.class, false, false); + if (validatorBeans.length == 0) { + registerMvcValidator(registry, beanFactory); + } + else if (validatorBeans.length == 1) { + registry.registerAlias(validatorBeans[0], "mvcValidator"); + } + else { + if (!ObjectUtils.containsElement(validatorBeans, "mvcValidator")) { + registerMvcValidator(registry, beanFactory); + } + } + } + + private void registerMvcValidator(BeanDefinitionRegistry registry, + ListableBeanFactory beanFactory) { + RootBeanDefinition definition = new RootBeanDefinition(); + definition.setBeanClass(getClass()); + definition.setFactoryMethodName("mvcValidator"); + registry.registerBeanDefinition("mvcValidator", definition); + } + + static Validator mvcValidator() { + Validator validator = new WebMvcConfigurationSupport().mvcValidator(); + try { + if (ClassUtils.forName(JSR303_VALIDATOR_CLASS, null) + .isInstance(validator)) { + return new DelegatingWebMvcValidator(validator); + } + } + catch (Exception ex) { + } + return validator; + } + + } + + /** + * {@link DelegatingValidator} for the MVC validator. + */ + static class DelegatingWebMvcValidator extends DelegatingValidator + implements ApplicationContextAware, InitializingBean, DisposableBean { + + public DelegatingWebMvcValidator(Validator targetValidator) { + super(targetValidator); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + if (getDelegate() instanceof ApplicationContextAware) { + ((ApplicationContextAware) getDelegate()) + .setApplicationContext(applicationContext); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + if (getDelegate() instanceof InitializingBean) { + ((InitializingBean) getDelegate()).afterPropertiesSet(); + } + } + + @Override + public void destroy() throws Exception { + if (getDelegate() instanceof DisposableBean) { + ((DisposableBean) getDelegate()).destroy(); + } + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcValidator.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcValidator.java deleted file mode 100644 index 499e08dbfdd..00000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcValidator.java +++ /dev/null @@ -1,144 +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.autoconfigure.web; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.validation.MessageInterpolatorFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.validation.Errors; -import org.springframework.validation.SmartValidator; -import org.springframework.validation.Validator; -import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean; -import org.springframework.validation.beanvalidation.SpringValidatorAdapter; - -/** - * A {@link SmartValidator} exposed as a bean for WebMvc use. Wraps existing - * {@link SpringValidatorAdapter} instances so that only the Spring's {@link Validator} - * type is exposed. This prevents such a bean to expose both the Spring and JSR-303 - * validator contract at the same time. - * - * @author Stephane Nicoll - * @author Phillip Webb - */ -class WebMvcValidator implements SmartValidator, ApplicationContextAware, - InitializingBean, DisposableBean { - - private final SpringValidatorAdapter target; - - private final boolean existingBean; - - WebMvcValidator(SpringValidatorAdapter target, boolean existingBean) { - this.target = target; - this.existingBean = existingBean; - } - - SpringValidatorAdapter getTarget() { - return this.target; - } - - @Override - public boolean supports(Class clazz) { - return this.target.supports(clazz); - } - - @Override - public void validate(Object target, Errors errors) { - this.target.validate(target, errors); - } - - @Override - public void validate(Object target, Errors errors, Object... validationHints) { - this.target.validate(target, errors, validationHints); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - if (!this.existingBean && this.target instanceof ApplicationContextAware) { - ((ApplicationContextAware) this.target) - .setApplicationContext(applicationContext); - } - } - - @Override - public void afterPropertiesSet() throws Exception { - if (!this.existingBean && this.target instanceof InitializingBean) { - ((InitializingBean) this.target).afterPropertiesSet(); - } - } - - @Override - public void destroy() throws Exception { - if (!this.existingBean && this.target instanceof DisposableBean) { - ((DisposableBean) this.target).destroy(); - } - } - - public static Validator get(ApplicationContext applicationContext, - Validator validator) { - if (validator != null) { - return wrap(validator, false); - } - return getExistingOrCreate(applicationContext); - } - - private static Validator getExistingOrCreate(ApplicationContext applicationContext) { - Validator existing = getExisting(applicationContext); - if (existing != null) { - return wrap(existing, true); - } - return create(); - } - - private static Validator getExisting(ApplicationContext applicationContext) { - try { - javax.validation.Validator validator = applicationContext - .getBean(javax.validation.Validator.class); - if (validator instanceof Validator) { - return (Validator) validator; - } - return new SpringValidatorAdapter(validator); - } - catch (NoSuchBeanDefinitionException ex) { - return null; - } - } - - private static Validator create() { - OptionalValidatorFactoryBean validator = new OptionalValidatorFactoryBean(); - validator.setMessageInterpolator(new MessageInterpolatorFactory().getObject()); - return wrap(validator, false); - } - - private static Validator wrap(Validator validator, boolean existingBean) { - if (validator instanceof javax.validation.Validator) { - if (validator instanceof SpringValidatorAdapter) { - return new WebMvcValidator((SpringValidatorAdapter) validator, - existingBean); - } - return new WebMvcValidator( - new SpringValidatorAdapter((javax.validation.Validator) validator), - existingBean); - } - return validator; - } - -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java index 07812e28911..b8d3fddfa16 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfigurationTests.java @@ -31,6 +31,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration; @@ -324,9 +325,9 @@ public class SpringBootWebSecurityConfigurationTests { @Documented @Import({ EmbeddedServletContainerAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, + WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, + ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) protected @interface MinimalWebConfiguration { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java new file mode 100644 index 00000000000..3ced1d921e9 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java @@ -0,0 +1,117 @@ +/* + * 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.autoconfigure.validation; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.SmartValidator; +import org.springframework.validation.Validator; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link DelegatingValidator}. + * + * @author Phillip Webb + */ +public class DelegatingValidatorTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Mock + private SmartValidator delegate; + + private DelegatingValidator delegating; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.delegating = new DelegatingValidator(this.delegate); + } + + @Test + public void createWhenJsrValidatorIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Target Validator must not be null"); + new DelegatingValidator((javax.validation.Validator) null); + } + + @Test + public void createWithJsrValidatorShouldAdapt() throws Exception { + javax.validation.Validator delegate = mock(javax.validation.Validator.class); + Validator delegating = new DelegatingValidator(delegate); + Object target = new Object(); + Errors errors = new BeanPropertyBindingResult(target, "foo"); + delegating.validate(target, errors); + verify(delegate).validate(any()); + } + + @Test + public void createWithSpringValidatorWhenValidatorIsNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Target Validator must not be null"); + new DelegatingValidator((Validator) null); + } + + @Test + public void supportsShouldDelegateToValidator() throws Exception { + this.delegating.supports(Object.class); + verify(this.delegate).supports(Object.class); + } + + @Test + public void validateShouldDelegateToValidator() throws Exception { + Object target = new Object(); + Errors errors = new BeanPropertyBindingResult(target, "foo"); + this.delegating.validate(target, errors); + verify(this.delegate).validate(target, errors); + } + + @Test + public void validateWithHintsShouldDelegateToValidator() throws Exception { + Object target = new Object(); + Errors errors = new BeanPropertyBindingResult(target, "foo"); + Object[] hints = { "foo", "bar" }; + this.delegating.validate(target, errors, hints); + verify(this.delegate).validate(target, errors, hints); + ; + } + + @Test + public void validateWithHintsWhenDelegateIsNotSmartShouldDelegateToSimpleValidator() + throws Exception { + Validator delegate = mock(Validator.class); + DelegatingValidator delegating = new DelegatingValidator(delegate); + Object target = new Object(); + Errors errors = new BeanPropertyBindingResult(target, "foo"); + Object[] hints = { "foo", "bar" }; + delegating.validate(target, errors, hints); + verify(delegate).validate(target, errors); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java index 1ea207316ca..df2e7e3cd9e 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java @@ -32,9 +32,12 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.validation.annotation.Validated; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; +import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link ValidationAutoConfiguration}. @@ -56,45 +59,94 @@ public class ValidationAutoConfigurationTests { } @Test - public void validationIsEnabled() { - load(SampleService.class); - assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - SampleService service = this.context.getBean(SampleService.class); - service.doSomething("Valid"); - this.thrown.expect(ConstraintViolationException.class); - service.doSomething("KO"); + public void validationAutoConfigurationShouldConfigureJsrAndSpringValidator() + throws Exception { + load(Config.class); + Validator jsrValidator = this.context.getBean(Validator.class); + String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); + org.springframework.validation.Validator springValidator = this.context + .getBean(org.springframework.validation.Validator.class); + String[] springValidatorNames = this.context + .getBeanNamesForType(org.springframework.validation.Validator.class); + assertThat(jsrValidator).isInstanceOf(LocalValidatorFactoryBean.class); + assertThat(jsrValidator).isEqualTo(springValidator); + assertThat(jsrValidatorNames).containsExactly("defaultValidator"); + assertThat(springValidatorNames).containsExactly("defaultValidator"); } @Test - public void validationUsesCglibProxy() { - load(DefaultAnotherSampleService.class); - assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - DefaultAnotherSampleService service = this.context - .getBean(DefaultAnotherSampleService.class); - service.doSomething(42); - this.thrown.expect(ConstraintViolationException.class); - service.doSomething(2); + public void validationAutoConfigurationWhenUserProvidesValidatorShouldBackOff() + throws Exception { + load(UserDefinedValidatorConfig.class); + Validator jsrValidator = this.context.getBean(Validator.class); + String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); + org.springframework.validation.Validator springValidator = this.context + .getBean(org.springframework.validation.Validator.class); + String[] springValidatorNames = this.context + .getBeanNamesForType(org.springframework.validation.Validator.class); + assertThat(jsrValidator).isInstanceOf(OptionalValidatorFactoryBean.class); + assertThat(jsrValidator).isEqualTo(springValidator); + assertThat(jsrValidatorNames).containsExactly("customValidator"); + assertThat(springValidatorNames).containsExactly("customValidator"); } @Test - public void validationCanBeConfiguredToUseJdkProxy() { + public void validationAutoConfigurationWhenUserProvidesJsrOnlyShouldAdaptIt() + throws Exception { + load(UserDefinedJsrValidatorConfig.class); + Validator jsrValidator = this.context.getBean(Validator.class); + String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class); + org.springframework.validation.Validator springValidator = this.context + .getBean(org.springframework.validation.Validator.class); + String[] springValidatorNames = this.context + .getBeanNamesForType(org.springframework.validation.Validator.class); + assertThat(jsrValidator).isNotEqualTo(springValidator); + assertThat(springValidator).isInstanceOf(DelegatingValidator.class); + assertThat(jsrValidatorNames).containsExactly("customValidator"); + assertThat(springValidatorNames).containsExactly("jsr303ValidatorAdapter"); + } + + @Test + public void validationAutoConfigurationShouldBeEnabled() { + load(ClassWithConstraint.class); + assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); + ClassWithConstraint service = this.context.getBean(ClassWithConstraint.class); + service.call("Valid"); + this.thrown.expect(ConstraintViolationException.class); + service.call("KO"); + } + + @Test + public void validationAutoConfigurationShouldUseCglibProxy() { + load(ImplementationOfInterfaceWithConstraint.class); + assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); + ImplementationOfInterfaceWithConstraint service = this.context + .getBean(ImplementationOfInterfaceWithConstraint.class); + service.call(42); + this.thrown.expect(ConstraintViolationException.class); + service.call(2); + } + + @Test + public void validationAutoConfigurationWhenProxyTargetClassIsFalseShouldUseJdkProxy() { load(AnotherSampleServiceConfiguration.class, "spring.aop.proxy-target-class=false"); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - assertThat(this.context.getBeansOfType(DefaultAnotherSampleService.class)) - .isEmpty(); - AnotherSampleService service = this.context.getBean(AnotherSampleService.class); - service.doSomething(42); + assertThat(this.context + .getBeansOfType(ImplementationOfInterfaceWithConstraint.class)).isEmpty(); + InterfaceWithConstraint service = this.context + .getBean(InterfaceWithConstraint.class); + service.call(42); this.thrown.expect(ConstraintViolationException.class); - service.doSomething(2); + service.call(2); } @Test - public void userDefinedMethodValidationPostProcessorTakesPrecedence() { - load(SampleConfiguration.class); + public void validationAutoConfigurationWhenUserDefinesMethodValidationPostProcessorShouldBackOff() { + load(UserDefinedMethodValidationConfig.class); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); Object userMethodValidationPostProcessor = this.context - .getBean("testMethodValidationPostProcessor"); + .getBean("customMethodValidationPostProcessor"); assertThat(this.context.getBean(MethodValidationPostProcessor.class)) .isSameAs(userMethodValidationPostProcessor); assertThat(this.context.getBeansOfType(MethodValidationPostProcessor.class)) @@ -115,47 +167,73 @@ public class ValidationAutoConfigurationTests { this.context = ctx; } - @Validated - static class SampleService { + @Configuration + static class Config { - public void doSomething(@Size(min = 3, max = 10) String name) { + } + @Configuration + static class UserDefinedValidatorConfig { + + @Bean + public OptionalValidatorFactoryBean customValidator() { + return new OptionalValidatorFactoryBean(); } } - interface AnotherSampleService { + @Configuration + static class UserDefinedJsrValidatorConfig { + + @Bean + public Validator customValidator() { + return mock(Validator.class); + } - void doSomething(@Min(42) Integer counter); } - @Validated - static class DefaultAnotherSampleService implements AnotherSampleService { - - @Override - public void doSomething(Integer counter) { + @Configuration + static class UserDefinedMethodValidationConfig { + @Bean + public MethodValidationPostProcessor customMethodValidationPostProcessor() { + return new MethodValidationPostProcessor(); } + } @Configuration static class AnotherSampleServiceConfiguration { @Bean - public AnotherSampleService anotherSampleService() { - return new DefaultAnotherSampleService(); + public InterfaceWithConstraint implementationOfInterfaceWithConstraint() { + return new ImplementationOfInterfaceWithConstraint(); } } - @Configuration - static class SampleConfiguration { + @Validated + static class ClassWithConstraint { + + public void call(@Size(min = 3, max = 10) String name) { - @Bean - public MethodValidationPostProcessor testMethodValidationPostProcessor() { - return new MethodValidationPostProcessor(); } } + interface InterfaceWithConstraint { + + void call(@Min(42) Integer counter); + } + + @Validated + static class ImplementationOfInterfaceWithConstraint + implements InterfaceWithConstraint { + + @Override + public void call(Integer counter) { + + } + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java index 4879481093a..29bb265dbdc 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java @@ -35,6 +35,7 @@ import org.junit.rules.ExpectedException; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.util.ApplicationContextTestUtils; import org.springframework.context.annotation.Configuration; @@ -126,9 +127,9 @@ public class BasicErrorControllerDirectMockMvcTests { @Documented @Import({ EmbeddedServletContainerAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, + WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, + ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) protected @interface MinimalWebConfiguration { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerMockMvcTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerMockMvcTests.java index 3ed4e8a9a0c..da5a0c5820c 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerMockMvcTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerMockMvcTests.java @@ -35,6 +35,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -128,9 +129,9 @@ public class BasicErrorControllerMockMvcTests { @Import({ EmbeddedServletContainerAutoConfiguration.EmbeddedTomcat.class, EmbeddedServletContainerAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class, + WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, + ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) protected @interface MinimalWebConfiguration { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index 724486d37d5..ca0355ce42e 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -27,7 +27,6 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.validation.ValidatorFactory; import org.assertj.core.api.Condition; import org.joda.time.DateTime; @@ -37,8 +36,11 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.validation.DelegatingValidator; +import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WelcomePageHandlerMapping; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; @@ -59,11 +61,11 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; -import org.springframework.validation.beanvalidation.SpringValidatorAdapter; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.filter.HttpPutFormContentFilter; @@ -655,77 +657,154 @@ public class WebMvcAutoConfigurationTests { } @Test - public void validationNoJsr303ValidatorExposedByDefault() { + public void validatorWhenSuppliedByConfigurerShouldThrowException() throws Exception { + this.thrown.expect(BeanCreationException.class); + this.thrown.expectMessage("unexpected validator configuration"); + load(ValidatorWebMvcConfigurer.class); + } + + @Test + public void validatorWhenAutoConfiguredShouldUseAlias() throws Exception { load(); - assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); - assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) - .isEmpty(); - assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); + Object defaultValidator = this.context.getBean("defaultValidator"); + Object mvcValidator = this.context.getBean("mvcValidator"); + String[] jsrValidatorBeans = this.context + .getBeanNamesForType(javax.validation.Validator.class); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(mvcValidator).isSameAs(defaultValidator); + assertThat(springValidatorBeans).containsExactly("defaultValidator"); + assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); } @Test - public void validationCustomConfigurerTakesPrecedence() { - load(MvcValidator.class); - assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); - assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) - .isEmpty(); - assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - Validator validator = this.context.getBean(Validator.class); - assertThat(validator) - .isSameAs(this.context.getBean(MvcValidator.class).validator); + public void validatorWhenUserDefinedSpringOnlyShouldUseDefined() throws Exception { + load(UserDefinedSpringOnlyValidator.class); + Object customValidator = this.context.getBean("customValidator"); + Object mvcValidator = this.context.getBean("mvcValidator"); + String[] jsrValidatorBeans = this.context + .getBeanNamesForType(javax.validation.Validator.class); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(mvcValidator).isSameAs(customValidator); + assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator); + assertThat(springValidatorBeans).containsExactly("customValidator"); + assertThat(jsrValidatorBeans).isEmpty(); } @Test - public void validationCustomConfigurerTakesPrecedenceAndDoNotExposeJsr303() { - load(MvcJsr303Validator.class); - assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); - assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) - .isEmpty(); - assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - Validator validator = this.context.getBean(Validator.class); - assertThat(validator).isInstanceOf(WebMvcValidator.class); - assertThat(((WebMvcValidator) validator).getTarget()) - .isSameAs(this.context.getBean(MvcJsr303Validator.class).validator); + public void validatorWhenUserDefinedJsr303ShouldAdapt() throws Exception { + load(UserDefinedJsr303Validator.class); + Object customValidator = this.context.getBean("customValidator"); + Object mvcValidator = this.context.getBean("mvcValidator"); + String[] jsrValidatorBeans = this.context + .getBeanNamesForType(javax.validation.Validator.class); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(mvcValidator).isNotSameAs(customValidator); + assertThat(this.context.getBean(javax.validation.Validator.class)) + .isEqualTo(customValidator); + assertThat(springValidatorBeans).containsExactly("jsr303ValidatorAdapter"); + assertThat(jsrValidatorBeans).containsExactly("customValidator"); } @Test - public void validationJsr303CustomValidatorReusedAsSpringValidator() { - load(CustomValidator.class); - assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1); - assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) - .hasSize(1); - assertThat(this.context.getBeansOfType(Validator.class)).hasSize(2); - Validator validator = this.context.getBean("mvcValidator", Validator.class); - assertThat(validator).isInstanceOf(WebMvcValidator.class); - assertThat(((WebMvcValidator) validator).getTarget()) - .isSameAs(this.context.getBean(javax.validation.Validator.class)); + public void validatorWhenUserDefinedSingleJsr303AndSpringShouldUseDefined() + throws Exception { + load(UserDefinedSingleJsr303AndSpringValidator.class); + Object customValidator = this.context.getBean("customValidator"); + Object mvcValidator = this.context.getBean("mvcValidator"); + String[] jsrValidatorBeans = this.context + .getBeanNamesForType(javax.validation.Validator.class); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(mvcValidator).isSameAs(customValidator); + assertThat(this.context.getBean(javax.validation.Validator.class)) + .isEqualTo(customValidator); + assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator); + assertThat(springValidatorBeans).containsExactly("customValidator"); + assertThat(jsrValidatorBeans).containsExactly("customValidator"); } @Test - public void validationJsr303ValidatorExposedAsSpringValidator() { - load(Jsr303Validator.class); - assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); - assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) - .hasSize(1); - assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); - Validator validator = this.context.getBean(Validator.class); - assertThat(validator).isInstanceOf(WebMvcValidator.class); - SpringValidatorAdapter target = ((WebMvcValidator) validator) - .getTarget(); - assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator")) - .isSameAs(this.context.getBean(javax.validation.Validator.class)); + public void validatorWhenUserDefinedJsr303AndSpringShouldUseDefined() + throws Exception { + load(UserDefinedJsr303AndSpringValidator.class); + Object customJsrValidator = this.context.getBean("customJsrValidator"); + Object customSpringValidator = this.context.getBean("customSpringValidator"); + Object mvcValidator = this.context.getBean("mvcValidator"); + String[] jsrValidatorBeans = this.context + .getBeanNamesForType(javax.validation.Validator.class); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(customJsrValidator).isNotSameAs(customSpringValidator); + assertThat(mvcValidator).isSameAs(customSpringValidator); + assertThat(this.context.getBean(javax.validation.Validator.class)) + .isEqualTo(customJsrValidator); + assertThat(this.context.getBean(Validator.class)) + .isEqualTo(customSpringValidator); + assertThat(springValidatorBeans).containsExactly("customSpringValidator"); + assertThat(jsrValidatorBeans).containsExactly("customJsrValidator"); + } + + @Test + public void validatorWhenExcludingValidatorAutoConfigurationShouldUseMvc() + throws Exception { + load(null, new Class[] { ValidationAutoConfiguration.class }); + Object mvcValidator = this.context.getBean("mvcValidator"); + String[] jsrValidatorBeans = this.context + .getBeanNamesForType(javax.validation.Validator.class); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(mvcValidator).isInstanceOf(DelegatingValidator.class); + assertThat(springValidatorBeans).containsExactly("mvcValidator"); + assertThat(jsrValidatorBeans).isEmpty(); + } + + @Test + public void validatorWhenMultipleValidatorsAndNoMvcValidatorShouldAddMvc() + throws Exception { + load(MultipleValidatorsAndNoMvcValidator.class); + Object customValidator1 = this.context.getBean("customValidator1"); + Object customValidator2 = this.context.getBean("customValidator2"); + Object mvcValidator = this.context.getBean("mvcValidator"); + String[] jsrValidatorBeans = this.context + .getBeanNamesForType(javax.validation.Validator.class); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(mvcValidator).isNotSameAs(customValidator1) + .isNotSameAs(customValidator2); + assertThat(springValidatorBeans).containsExactly("customValidator1", + "customValidator2", "mvcValidator"); + assertThat(jsrValidatorBeans).isEmpty(); + } + + @Test + public void validatorWhenMultipleValidatorsAndMvcValidatorShouldUseMvc() + throws Exception { + load(MultipleValidatorsAndMvcValidator.class); + Object customValidator = this.context.getBean("customValidator"); + Object mvcValidator = this.context.getBean("mvcValidator"); + String[] jsrValidatorBeans = this.context + .getBeanNamesForType(javax.validation.Validator.class); + String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); + assertThat(mvcValidator).isNotSameAs(customValidator); + assertThat(springValidatorBeans).containsExactly("customValidator", + "mvcValidator"); + assertThat(jsrValidatorBeans).isEmpty(); } private void load(Class config, String... environment) { + load(config, null, environment); + } + + private void load(Class config, Class[] exclude, String... environment) { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, environment); List> configClasses = new ArrayList>(); if (config != null) { configClasses.add(config); } - configClasses.addAll(Arrays.asList(Config.class, WebMvcAutoConfiguration.class, + configClasses.addAll(Arrays.asList(Config.class, + ValidationAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class)); + if (!ObjectUtils.isEmpty(exclude)) { + configClasses.removeAll(Arrays.asList(exclude)); + } this.context.register(configClasses.toArray(new Class[configClasses.size()])); this.context.refresh(); } @@ -895,47 +974,88 @@ public class WebMvcAutoConfigurationTests { } @Configuration - protected static class MvcValidator extends WebMvcConfigurerAdapter { - - private final Validator validator = mock(Validator.class); + protected static class ValidatorWebMvcConfigurer extends WebMvcConfigurerAdapter { @Override public Validator getValidator() { - return this.validator; + return mock(Validator.class); } } @Configuration - protected static class MvcJsr303Validator extends WebMvcConfigurerAdapter { - - private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); - - @Override - public Validator getValidator() { - return this.validator; - } - - } - - @Configuration - static class Jsr303Validator { + static class UserDefinedSpringOnlyValidator { @Bean - public javax.validation.Validator jsr303Validator() { + public Validator customValidator() { + return mock(Validator.class); + } + + } + + @Configuration + static class UserDefinedJsr303Validator { + + @Bean + public javax.validation.Validator customValidator() { return mock(javax.validation.Validator.class); } } @Configuration - static class CustomValidator { + static class UserDefinedSingleJsr303AndSpringValidator { @Bean - public Validator customValidator() { + public LocalValidatorFactoryBean customValidator() { return new LocalValidatorFactoryBean(); } } + @Configuration + static class UserDefinedJsr303AndSpringValidator { + + @Bean + public javax.validation.Validator customJsrValidator() { + return mock(javax.validation.Validator.class); + } + + @Bean + public Validator customSpringValidator() { + return mock(Validator.class); + } + + } + + @Configuration + static class MultipleValidatorsAndNoMvcValidator { + + @Bean + public Validator customValidator1() { + return mock(Validator.class); + } + + @Bean + public Validator customValidator2() { + return mock(Validator.class); + } + + } + + @Configuration + static class MultipleValidatorsAndMvcValidator { + + @Bean + public Validator customValidator() { + return mock(Validator.class); + } + + @Bean + public Validator mvcValidator() { + return mock(Validator.class); + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcValidatorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcValidatorTests.java deleted file mode 100644 index a0df0cf2393..00000000000 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcValidatorTests.java +++ /dev/null @@ -1,152 +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.autoconfigure.web; - -import java.util.HashMap; - -import javax.validation.constraints.Min; - -import org.junit.After; -import org.junit.Test; - -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.validation.MapBindingResult; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link WebMvcValidator}. - * - * @author Stephane Nicoll - */ -public class WebMvcValidatorTests { - - private AnnotationConfigApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void wrapLocalValidatorFactoryBean() { - WebMvcValidator wrapper = load( - LocalValidatorFactoryBeanConfig.class); - assertThat(wrapper.supports(SampleData.class)).isTrue(); - MapBindingResult errors = new MapBindingResult(new HashMap(), - "test"); - wrapper.validate(new SampleData(40), errors); - assertThat(errors.getErrorCount()).isEqualTo(1); - } - - @Test - public void wrapperInvokesCallbackOnNonManagedBean() { - load(NonManagedBeanConfig.class); - LocalValidatorFactoryBean validator = this.context - .getBean(NonManagedBeanConfig.class).validator; - verify(validator, times(1)).setApplicationContext(any(ApplicationContext.class)); - verify(validator, times(1)).afterPropertiesSet(); - verify(validator, times(0)).destroy(); - this.context.close(); - this.context = null; - verify(validator, times(1)).destroy(); - } - - @Test - public void wrapperDoesNotInvokeCallbackOnManagedBean() { - load(ManagedBeanConfig.class); - LocalValidatorFactoryBean validator = this.context - .getBean(ManagedBeanConfig.class).validator; - verify(validator, times(0)).setApplicationContext(any(ApplicationContext.class)); - verify(validator, times(0)).afterPropertiesSet(); - verify(validator, times(0)).destroy(); - this.context.close(); - this.context = null; - verify(validator, times(0)).destroy(); - } - - private WebMvcValidator load(Class config) { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - ctx.register(config); - ctx.refresh(); - this.context = ctx; - return this.context.getBean(WebMvcValidator.class); - } - - @Configuration - static class LocalValidatorFactoryBeanConfig { - - @Bean - public LocalValidatorFactoryBean validator() { - return new LocalValidatorFactoryBean(); - } - - @Bean - public WebMvcValidator wrapper() { - return new WebMvcValidator(validator(), true); - } - - } - - @Configuration - static class NonManagedBeanConfig { - - private final LocalValidatorFactoryBean validator = mock( - LocalValidatorFactoryBean.class); - - @Bean - public WebMvcValidator wrapper() { - return new WebMvcValidator(this.validator, false); - } - - } - - @Configuration - static class ManagedBeanConfig { - - private final LocalValidatorFactoryBean validator = mock( - LocalValidatorFactoryBean.class); - - @Bean - public WebMvcValidator wrapper() { - return new WebMvcValidator(this.validator, true); - } - - } - - static class SampleData { - - @Min(42) - private int counter; - - SampleData(int counter) { - this.counter = counter; - } - - } - -} From 5abc050a96d9a4a10e59bbf8ed92e15e6acdea3a Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 19 Apr 2017 17:15:59 -0700 Subject: [PATCH 2/5] Support detection and with test initializers Relax `SpringBootTestContextBootstrapper` rules so that a test can specify an `ApplicationContextInitializer` and still have `@SpringBootConfiguration` detected. Prior to this commit detection would not occur because it's possible that an initializer _could_ register configuration. This scenario is actually quite unlikely to occur, certainly less likely than wanting to use an initializer in combination with auto-detection. Fixes gh-8483 --- .../validation/DelegatingValidator.java | 4 ++ .../SpringBootTestContextBootstrapper.java | 3 +- ...textBootstrapperWithInitializersTests.java | 66 +++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperWithInitializersTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java index dc9fc725b6b..e180ba284a4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/DelegatingValidator.java @@ -71,6 +71,10 @@ public class DelegatingValidator implements SmartValidator { } } + /** + * Return the delegate validator. + * @return the delegate validator + */ protected final Validator getDelegate() { return this.delegate; } diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java index 15daa0c9e83..361bde0f7ef 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java @@ -194,8 +194,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr protected Class[] getOrFindConfigurationClasses( MergedContextConfiguration mergedConfig) { Class[] classes = mergedConfig.getClasses(); - if (containsNonTestComponent(classes) || mergedConfig.hasLocations() - || !mergedConfig.getContextInitializerClasses().isEmpty()) { + if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) { return classes; } Class found = new SpringBootConfigurationFinder() diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperWithInitializersTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperWithInitializersTests.java new file mode 100644 index 00000000000..c572dbbf199 --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperWithInitializersTests.java @@ -0,0 +1,66 @@ +/* + * 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.test.context.bootstrap; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; +import org.springframework.boot.test.context.bootstrap.SpringBootTestContextBootstrapperWithInitializersTests.CustomInitializer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link SpringBootTestContextBootstrapper} with and + * {@link ApplicationContextInitializer}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@BootstrapWith(SpringBootTestContextBootstrapper.class) +@ContextConfiguration(initializers = CustomInitializer.class) +public class SpringBootTestContextBootstrapperWithInitializersTests { + + @Autowired + private ApplicationContext context; + + @Test + public void foundConfiguration() throws Exception { + Object bean = this.context + .getBean(SpringBootTestContextBootstrapperExampleConfig.class); + assertThat(bean).isNotNull(); + } + + // gh-8483 + + public static class CustomInitializer + implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + } + + } + +} From 14638e67bc57645aecd38f15262e0daf490dd2c1 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 3 Mar 2017 16:44:43 +0000 Subject: [PATCH 3/5] Extended PropertiesLauncher class location logic Update `PropertiesLauncher` so that classes can be loaded outside of `BOOT-INF/classes`. You can use a subdirectory, or the root directory of an external jar (but not the parent archive to avoid issues with agents and awkward delegation models). Fixes gh-8480 Closes gh-8486 --- .../web/WebMvcAutoConfiguration.java | 2 +- .../validation/DelegatingValidatorTests.java | 1 - .../appendix-executable-jar-format.adoc | 2 + .../boot/loader/PropertiesLauncher.java | 77 +++++++++++----- .../boot/loader/PropertiesLauncherTests.java | 82 +++++++++++++++++- .../src/test/resources/nested-jars/app.jar | Bin 3081 -> 3313 bytes 6 files changed, 136 insertions(+), 28 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index f08693eaca0..a51f65a06d5 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -735,7 +735,7 @@ public class WebMvcAutoConfiguration { static class DelegatingWebMvcValidator extends DelegatingValidator implements ApplicationContextAware, InitializingBean, DisposableBean { - public DelegatingWebMvcValidator(Validator targetValidator) { + DelegatingWebMvcValidator(Validator targetValidator) { super(targetValidator); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java index 3ced1d921e9..04cc4e8c075 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/DelegatingValidatorTests.java @@ -99,7 +99,6 @@ public class DelegatingValidatorTests { Object[] hints = { "foo", "bar" }; this.delegating.validate(target, errors, hints); verify(this.delegate).validate(target, errors, hints); - ; } @Test diff --git a/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc b/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc index 45fc7ba4184..7d8ac458eb1 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc @@ -283,6 +283,8 @@ the `Main-Class` attribute and leave out `Start-Class`. * `loader.path` can contain directories (scanned recursively for jar and zip files), archive paths, a directory within an archive that is scanned for jar files (for example, `dependencies.jar!/lib`), or wildcard patterns (for the default JVM behavior). + Archive paths can be relative to `loader.home`, or anywhere in the file system with a + `jar:file:` prefix. * `loader.path` (if empty) defaults to `BOOT-INF/lib` (meaning a local directory or a nested one if running from an archive). Because of this `PropertiesLauncher` behaves the same as `JarLauncher` when no additional configuration is provided. diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java index 01253279ac3..395af6eea8c 100755 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java @@ -25,8 +25,10 @@ import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Properties; +import java.util.Set; import java.util.jar.Manifest; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -299,11 +301,9 @@ public class PropertiesLauncher extends Launcher { List paths = new ArrayList(); for (String path : commaSeparatedPaths.split(",")) { path = cleanupPath(path); - // Empty path (i.e. the archive itself if running from a JAR) is always added - // to the classpath so no need for it to be explicitly listed - if (!path.equals("")) { - paths.add(path); - } + // "" means the user wants root of archive but not current directory + path = ("".equals(path) ? "/" : path); + paths.add(path); } if (paths.isEmpty()) { paths.add("lib"); @@ -336,7 +336,13 @@ public class PropertiesLauncher extends Launcher { @Override protected ClassLoader createClassLoader(List archives) throws Exception { - ClassLoader loader = super.createClassLoader(archives); + Set urls = new LinkedHashSet(archives.size()); + for (Archive archive : archives) { + urls.add(archive.getUrl()); + } + ClassLoader loader = new LaunchedURLClassLoader(urls.toArray(new URL[0]), + getClass().getClassLoader()); + debug("Classpath: " + urls); String customLoaderClassName = getProperty("loader.classLoader"); if (customLoaderClassName != null) { loader = wrapWithCustomClassLoader(loader, customLoaderClassName); @@ -454,13 +460,15 @@ public class PropertiesLauncher extends Launcher { String root = cleanupPath(stripFileUrlPrefix(path)); List lib = new ArrayList(); File file = new File(root); - if (!isAbsolutePath(root)) { - file = new File(this.home, root); - } - if (file.isDirectory()) { - debug("Adding classpath entries from " + file); - Archive archive = new ExplodedArchive(file, false); - lib.add(archive); + if (!"/".equals(root)) { + if (!isAbsolutePath(root)) { + file = new File(this.home, root); + } + if (file.isDirectory()) { + debug("Adding classpath entries from " + file); + Archive archive = new ExplodedArchive(file, false); + lib.add(archive); + } } Archive archive = getArchive(file); if (archive != null) { @@ -488,24 +496,46 @@ public class PropertiesLauncher extends Launcher { return null; } - private List getNestedArchives(String root) throws Exception { - if (root.startsWith("/") - || this.parent.getUrl().equals(this.home.toURI().toURL())) { + private List getNestedArchives(String path) throws Exception { + Archive parent = this.parent; + String root = path; + if (!root.equals("/") && root.startsWith("/") + || parent.getUrl().equals(this.home.toURI().toURL())) { // If home dir is same as parent archive, no need to add it twice. return null; } - Archive parent = this.parent; - if (root.startsWith("jar:file:") && root.contains("!")) { + if (root.contains("!")) { int index = root.indexOf("!"); - String file = root.substring("jar:file:".length(), index); - parent = new JarFileArchive(new File(file)); + File file = new File(this.home, root.substring(0, index)); + if (root.startsWith("jar:file:")) { + file = new File(root.substring("jar:file:".length(), index)); + } + parent = new JarFileArchive(file); root = root.substring(index + 1, root.length()); while (root.startsWith("/")) { root = root.substring(1); } } + if (root.endsWith(".jar")) { + File file = new File(this.home, root); + if (file.exists()) { + parent = new JarFileArchive(file); + root = ""; + } + } + if (root.equals("/") || root.equals("./") || root.equals(".")) { + // The prefix for nested jars is actually empty if it's at the root + root = ""; + } EntryFilter filter = new PrefixMatchingArchiveFilter(root); - return parent.getNestedArchives(filter); + List archives = new ArrayList(parent.getNestedArchives(filter)); + if (("".equals(root) || ".".equals(root)) && !path.endsWith(".jar") + && parent != this.parent) { + // You can't find the root with an entry filter so it has to be added + // explicitly. But don't add the root of the parent archive. + archives.add(parent); + } + return archives; } private void addNestedEntries(List lib) { @@ -518,7 +548,7 @@ public class PropertiesLauncher extends Launcher { @Override public boolean matches(Entry entry) { if (entry.isDirectory()) { - return entry.getName().startsWith(JarLauncher.BOOT_INF_CLASSES); + return entry.getName().equals(JarLauncher.BOOT_INF_CLASSES); } return entry.getName().startsWith(JarLauncher.BOOT_INF_LIB); } @@ -607,6 +637,9 @@ public class PropertiesLauncher extends Launcher { @Override public boolean matches(Entry entry) { + if (entry.isDirectory()) { + return entry.getName().equals(this.prefix); + } return entry.getName().startsWith(this.prefix) && this.filter.matches(entry); } diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java index 3610844a85a..98be63ec579 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java @@ -21,12 +21,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.jar.Attributes; import java.util.jar.Manifest; +import org.assertj.core.api.Condition; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -36,6 +37,9 @@ import org.junit.rules.TemporaryFolder; import org.mockito.MockitoAnnotations; import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.archive.ExplodedArchive; +import org.springframework.boot.loader.archive.JarFileArchive; +import org.springframework.core.io.FileSystemResource; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -72,6 +76,7 @@ public class PropertiesLauncherTests { System.clearProperty("loader.config.name"); System.clearProperty("loader.config.location"); System.clearProperty("loader.system"); + System.clearProperty("loader.classLoader"); } @Test @@ -131,6 +136,16 @@ public class PropertiesLauncherTests { .isEqualTo("[.]"); } + @Test + public void testUserSpecifiedSlashPath() throws Exception { + System.setProperty("loader.path", "jars/"); + PropertiesLauncher launcher = new PropertiesLauncher(); + assertThat(ReflectionTestUtils.getField(launcher, "paths").toString()) + .isEqualTo("[jars/]"); + List archives = launcher.getClassPathArchives(); + assertThat(archives).areExactly(1, endingWith("app.jar!/")); + } + @Test public void testUserSpecifiedWildcardPath() throws Exception { System.setProperty("loader.path", "jars/*"); @@ -153,13 +168,44 @@ public class PropertiesLauncherTests { waitFor("Hello World"); } + @Test + public void testUserSpecifiedRootOfJarPath() throws Exception { + System.setProperty("loader.path", + "jar:file:./src/test/resources/nested-jars/app.jar!/"); + PropertiesLauncher launcher = new PropertiesLauncher(); + assertThat(ReflectionTestUtils.getField(launcher, "paths").toString()) + .isEqualTo("[jar:file:./src/test/resources/nested-jars/app.jar!/]"); + List archives = launcher.getClassPathArchives(); + assertThat(archives).areExactly(1, endingWith("foo.jar!/")); + assertThat(archives).areExactly(1, endingWith("app.jar!/")); + } + + @Test + public void testUserSpecifiedRootOfJarPathWithDot() throws Exception { + System.setProperty("loader.path", "nested-jars/app.jar!/./"); + PropertiesLauncher launcher = new PropertiesLauncher(); + List archives = launcher.getClassPathArchives(); + assertThat(archives).areExactly(1, endingWith("foo.jar!/")); + assertThat(archives).areExactly(1, endingWith("app.jar!/")); + } + + @Test + public void testUserSpecifiedRootOfJarPathWithDotAndJarPrefix() throws Exception { + System.setProperty("loader.path", + "jar:file:./src/test/resources/nested-jars/app.jar!/./"); + PropertiesLauncher launcher = new PropertiesLauncher(); + List archives = launcher.getClassPathArchives(); + assertThat(archives).areExactly(1, endingWith("foo.jar!/")); + } + @Test public void testUserSpecifiedJarFileWithNestedArchives() throws Exception { System.setProperty("loader.path", "nested-jars/app.jar"); System.setProperty("loader.main", "demo.Application"); PropertiesLauncher launcher = new PropertiesLauncher(); - launcher.launch(new String[0]); - waitFor("Hello World"); + List archives = launcher.getClassPathArchives(); + assertThat(archives).areExactly(1, endingWith("foo.jar!/")); + assertThat(archives).areExactly(1, endingWith("app.jar!/")); } @Test @@ -209,11 +255,28 @@ public class PropertiesLauncherTests { public void testCustomClassLoaderCreation() throws Exception { System.setProperty("loader.classLoader", TestLoader.class.getName()); PropertiesLauncher launcher = new PropertiesLauncher(); - ClassLoader loader = launcher.createClassLoader(Collections.emptyList()); + ClassLoader loader = launcher.createClassLoader(archives()); assertThat(loader).isNotNull(); assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName()); } + private List archives() throws Exception { + List archives = new ArrayList(); + String path = System.getProperty("java.class.path"); + for (String url : path.split(File.pathSeparator)) { + archives.add(archive(url)); + } + return archives; + } + + private Archive archive(String url) throws IOException { + File file = new FileSystemResource(url).getFile(); + if (url.endsWith(".jar")) { + return new JarFileArchive(file); + } + return new ExplodedArchive(file); + } + @Test public void testUserSpecifiedConfigPathWins() throws Exception { @@ -280,6 +343,17 @@ public class PropertiesLauncherTests { assertThat(timeout).as("Timed out waiting for (" + value + ")").isTrue(); } + private Condition endingWith(final String value) { + return new Condition() { + + @Override + public boolean matches(Archive archive) { + return archive.toString().endsWith(value); + } + + }; + } + public static class TestLoader extends URLClassLoader { public TestLoader(ClassLoader parent) { diff --git a/spring-boot-tools/spring-boot-loader/src/test/resources/nested-jars/app.jar b/spring-boot-tools/spring-boot-loader/src/test/resources/nested-jars/app.jar index a4365eec85b5b8c296c9a6466973a15d818e96b2..5600ed279efb99189791ed00b3bbb4203efaa208 100644 GIT binary patch literal 3313 zcmc&$&u`pB6n@^_WV6nuNq;15Xxh2~q)k!0O}QYpMNPK}O_Q`y6M{s6%GsR+x4Rz8 z_NFZmp^5|Q2_a4hlmkM5I3U3R5E5MA)b@n<11fPqNN_+1aX}T|vmM*JF%=R*cr~_n z{NBv_@qO>j!fZCD67>*`oW6OM5?6t8G(WX8IX-i4x_s{z(L$T(z|Qwary$N_4@M+9 zKY4CudTMb=pP!yLEOS-3-uSX`TvJ+)YZLm>KDFuy!xQ!KQ`dRoG5zSJiDMI5>AYjq z8bYf|$Ci%aL4K@H?Nj4z4`1CpoiGsg5gE2!jwK$1#LuH2epJd3@#qUBrlr(t4a0SB z-TU%u%sonnRqCc)1=>w}dT3CkA=;a#eR&$r(*p{1A2%)2dqSaXX>3`cTvgVELj7~5 zCC;^)E5cbaRvMVeIxS0~q0+^<%f`AY{8pH8E|nY!4g3VhRmk zXjz`w6w9V-Vx39LlAiCdtI&}->8zDqn-^JijHb9MohxP66ONGHbz79HvS~|8SYD-w zv=#ESzepo=K%pJjK!ZURG<99i)4?JgB2A$@2MHe)8i>t2zj9gBJnWaY-HMbRZ`3rb zIy((}E#b83FJ6aDO@u%=_%Ij*z?9_$815kk!e~|$+U;-HJ`ID|JFkJz4VKa(M~A5k z>46|}mBAB!;trhB0X!8vyN-NL$|rbHs26t@#w6XKB9b16J`7c0fC`^dW>@wz%EjWU z|DnfUH$yuhl%aka2p8I!#5#lSi=Q?x3}XWxMF@rQ^s{LjwJXM&&@fj~O{B*0G=x;| z$e_+4)whN8zAACjSusqydNO)f?bb@etZ6>7HO@ZvZ-^$a;etjT+0$N93n$O7BVA@) zsD)5>U7B?UO(}paaHAQ{nmYz*EpY|e)}gG2xhsvf!)WM8>zNP8PLWrCy@zlf)V><3mpDW*vIK&?!hVC3;379J*H&9y$nQp!47^BG53(h zPVTXIz`Yz+?egn$4uU%{`tdD812HCU0~1ev@N463eBd#N(HXrq3i8KFJ1G)@4iO#V z-sOT@(|q_d3PSu}d@h8)_kts@x>^+d1Rp{!Ao4L661mHEN!8OyT}*w=!CutVwItTx&T( zpR)MOGl{usIOkX86VIxKZR^ugqTqMi@x+a4uW$&@%{p*nb$!8 literal 3081 zcmai$4LsBP9>@Q)&8AU#wkRb>9yj5JvRGKQ(HKeR-rNma30cFb$kXKr-8!dMhzLo~ zz3Ab@={nh*I>Mnj9y&R);=ClBhRQuB)&2dq@V~ax`S11H{{PqO|9kKAefWL9-#{iB zvj~DO>wRTDikpvLT96qO;2T19pl_j(w}xmzXb%)O*zfsFXwrTHgu=W=K@i3Oyc`G+ z@GoA0Fj^Mo>kXb@mH`qGK;1&8`38rO0%&tIn7Dv870}k|&vYP3b(jve?QI=5*p6M! zvR{QHMu0s%K$>(3qg6CY{Jx~fcGrK6KeHOHvUM9aHXJhktN=P0Gbx-9q7Z@_;>vu& z`Jz@>3>_>YQc!|G7`Lz7a_vy$g zh9W49n#I~Yy+>Bw`Rg{69zFac^!G=6!GYu1E>Ra|e)c#*d-nMF6PJ3wm=eOhfAkL( zTHR$7rN7PEJZTV&kvuGaubos z*y%-q`wRUIJ1^V~ll1v#PZnn9{>yCJR~ugW`_-~vOYJ;wi^j`K{P6;QPGan)LK-Tq zQg-qBAK#YejtB9~@6?P_`L@M%hhnaZo0qoS)cI%kl@a23{hqg?y_FUNXi=4GYwL@p zC67-eqhOnrncj7<3@!$97Z%tY#fytqrh0}*%>HCSX#?BW4yHn$z+E!1jApk}1%lY< zNN!?u{2oNk3jj}D&Ij&bYETU*XD^=}EvSw@Q%%A2coQ0yQGKLYA)GF6*5LZ)=CStc z*MdARuUM4)9QW#MOElld)xv|9;O7P=9 z;aEAfPU7syBa`2qqLq0B=hX?#ay~y`Ve@j05NC!xnuIfb6lGn?yc+Pf$0xa2nppFt z(Z!rj*Wb)-s5fqtagihUv~Lk#ZIGDNesIe>$08KlNJj?-?FPf8V{7A9emU?i+Ifky z-0^|S->&R?-)Gv_EbX;ggD$Z5{l;&*$Yb@Hz7%XqWky_)7!kByj4sSzXXhLA_>1ow?j&d)(vLziZ5Aa4QJM=JOM=X&{~@U>T|l$6PA!0nvchf>aJT zOj#^()TT|Jmb~a9HF&Mjw0yHzJ9m|3P_7`t?h69`^>3K(8~eUH^FvR6thUU%`hi?1 z9%`&G%v_#82-P>-Puw-bmxitnvpLYb?qpg|F~g%&++&S?eyei_=Yt^g!`PCkV3(m| zM$7Bn^Uu9pj5;RsGfZ{$9~kAYi+nO~3_UR2@06xx6dM?F8Y~{ z^P`SO-aofPqdmM6Zz0a9-K5>*n0r#EDq9y%$-L#e>QC>aiC5>Rhe+vnZy0x?D5xR)v*Cd z?n07PhX{aIGe98ypxXYCeux43mBRoasc8jB7pRT`vn1FB7eA5^g8@LogMr!{P Date: Wed, 19 Apr 2017 19:52:57 -0700 Subject: [PATCH 4/5] Upgrade to Spring Data Ingalls-SR3 Closes gh-8939 --- spring-boot-dependencies/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 8e564bda240..9ff600dfcd9 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -155,7 +155,7 @@ 1.7.2.RELEASE 1.2.3.RELEASE 3.0.7.RELEASE - Ingalls-BUILD-SNAPSHOT + Ingalls-SR3 0.23.0.RELEASE 4.3.9.RELEASE 1.2.1.RELEASE From bddc1908483f4b26a9c9bcf2e20c2c95822d774b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 19 Apr 2017 22:13:28 -0700 Subject: [PATCH 5/5] Suppress "resolving dependencies" when --quiet Update Spring CLI so that the "resolving dependencies" message is suppressed when `run --quiet` is used. Fixes gh-8946 --- .../OptionSetGroovyCompilerConfiguration.java | 5 +++++ .../boot/cli/command/run/RunCommand.java | 7 ++++++- .../boot/cli/compiler/GroovyCompiler.java | 3 ++- .../cli/compiler/GroovyCompilerConfiguration.java | 6 ++++++ .../boot/cli/compiler/grape/AetherGrapeEngine.java | 12 +++++++----- .../cli/compiler/grape/AetherGrapeEngineFactory.java | 10 ++-------- .../install/GroovyGrabDependencyResolverTests.java | 5 +++++ .../cli/compiler/grape/AetherGrapeEngineTests.java | 2 +- 8 files changed, 34 insertions(+), 16 deletions(-) diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionSetGroovyCompilerConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionSetGroovyCompilerConfiguration.java index b3e0ee79194..d4a6b2ec19c 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionSetGroovyCompilerConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionSetGroovyCompilerConfiguration.java @@ -92,4 +92,9 @@ public class OptionSetGroovyCompilerConfiguration implements GroovyCompilerConfi return this.repositoryConfiguration; } + @Override + public boolean isQuiet() { + return false; + } + } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java index 97b32f30cc9..ed480ed6f60 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java @@ -140,7 +140,7 @@ public class RunCommand extends OptionParsingCommand { @Override public Level getLogLevel() { - if (getOptions().has(RunOptionHandler.this.quietOption)) { + if (isQuiet()) { return Level.OFF; } if (getOptions().has(RunOptionHandler.this.verboseOption)) { @@ -149,6 +149,11 @@ public class RunCommand extends OptionParsingCommand { return Level.INFO; } + @Override + public boolean isQuiet() { + return getOptions().has(RunOptionHandler.this.quietOption); + } + } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java index 7b4db23c275..0f7ba330bcd 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java @@ -95,7 +95,8 @@ public class GroovyCompiler { new SpringBootDependenciesDependencyManagement()); AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(this.loader, - configuration.getRepositoryConfiguration(), resolutionContext); + configuration.getRepositoryConfiguration(), resolutionContext, + configuration.isQuiet()); GrapeEngineInstaller.install(grapeEngine); diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java index 7741f3242cf..7fd8462dc86 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompilerConfiguration.java @@ -71,4 +71,10 @@ public interface GroovyCompilerConfiguration { */ List getRepositoryConfiguration(); + /** + * Returns if running in quiet mode. + * @return {@code true} if running in quiet mode + */ + boolean isQuiet(); + } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java index c62d40f98da..2342b44f035 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java @@ -77,7 +77,7 @@ public class AetherGrapeEngine implements GrapeEngine { RepositorySystem repositorySystem, DefaultRepositorySystemSession repositorySystemSession, List remoteRepositories, - DependencyResolutionContext resolutionContext) { + DependencyResolutionContext resolutionContext, boolean quiet) { this.classLoader = classLoader; this.repositorySystem = repositorySystem; this.session = repositorySystemSession; @@ -89,12 +89,14 @@ public class AetherGrapeEngine implements GrapeEngine { for (RemoteRepository repository : remotes) { addRepository(repository); } - this.progressReporter = getProgressReporter(this.session); + this.progressReporter = getProgressReporter(this.session, quiet); } - private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session) { - String progressReporter = System.getProperty( - "org.springframework.boot.cli.compiler.grape.ProgressReporter"); + private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session, + boolean quiet) { + String progressReporter = (quiet ? "none" + : System.getProperty( + "org.springframework.boot.cli.compiler.grape.ProgressReporter")); if ("detail".equals(progressReporter) || Boolean.getBoolean("groovy.grape.report.downloads")) { return new DetailedProgressReporter(session, System.out); diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java index 13017518c14..4054e780e07 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java @@ -44,27 +44,21 @@ public abstract class AetherGrapeEngineFactory { public static AetherGrapeEngine create(GroovyClassLoader classLoader, List repositoryConfigurations, - DependencyResolutionContext dependencyResolutionContext) { - + DependencyResolutionContext dependencyResolutionContext, boolean quiet) { RepositorySystem repositorySystem = createServiceLocator() .getService(RepositorySystem.class); - DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils .newSession(); - ServiceLoader autoConfigurations = ServiceLoader .load(RepositorySystemSessionAutoConfiguration.class); - for (RepositorySystemSessionAutoConfiguration autoConfiguration : autoConfigurations) { autoConfiguration.apply(repositorySystemSession, repositorySystem); } - new DefaultRepositorySystemSessionAutoConfiguration() .apply(repositorySystemSession, repositorySystem); - return new AetherGrapeEngine(classLoader, repositorySystem, repositorySystemSession, createRepositories(repositoryConfigurations), - dependencyResolutionContext); + dependencyResolutionContext, quiet); } private static ServiceLocator createServiceLocator() { diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java index 5803df1bfcd..6920683a2a7 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/install/GroovyGrabDependencyResolverTests.java @@ -81,6 +81,11 @@ public class GroovyGrabDependencyResolverTests { return new String[] { "." }; } + @Override + public boolean isQuiet() { + return false; + } + }; this.resolver = new GroovyGrabDependencyResolver(configuration); } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java index 304e2b42731..036ed9b7eb2 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineTests.java @@ -59,7 +59,7 @@ public class AetherGrapeEngineTests { dependencyResolutionContext.addDependencyManagement( new SpringBootDependenciesDependencyManagement()); return AetherGrapeEngineFactory.create(this.groovyClassLoader, - repositoryConfigurations, dependencyResolutionContext); + repositoryConfigurations, dependencyResolutionContext, false); } @Test