Commit 81fef71f authored by Phillip Webb's avatar Phillip Webb

Merge branch '1.5.x'

parents 359854eb bddc1908
...@@ -42,6 +42,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean ...@@ -42,6 +42,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
...@@ -85,15 +86,16 @@ public class EndpointMvcIntegrationTests { ...@@ -85,15 +86,16 @@ public class EndpointMvcIntegrationTests {
@Test @Test
public void envEndpointNotHidden() throws InterruptedException { public void envEndpointNotHidden() throws InterruptedException {
String body = new TestRestTemplate().getForObject( String body = new TestRestTemplate().getForObject(
"http://localhost:" + this.port + "/application/env/foo.bar", String.class); "http://localhost:" + this.port + "/application/env/foo.bar",
String.class);
assertThat(body).isNotNull().contains("\"baz\""); assertThat(body).isNotNull().contains("\"baz\"");
assertThat(this.interceptor.invoked()).isTrue(); assertThat(this.interceptor.invoked()).isTrue();
} }
@Test @Test
public void healthEndpointNotHidden() throws InterruptedException { public void healthEndpointNotHidden() throws InterruptedException {
String body = new TestRestTemplate() String body = new TestRestTemplate().getForObject(
.getForObject("http://localhost:" + this.port + "/application/health", String.class); "http://localhost:" + this.port + "/application/health", String.class);
assertThat(body).isNotNull().contains("status"); assertThat(body).isNotNull().contains("status");
assertThat(this.interceptor.invoked()).isTrue(); assertThat(this.interceptor.invoked()).isTrue();
} }
...@@ -153,9 +155,9 @@ public class EndpointMvcIntegrationTests { ...@@ -153,9 +155,9 @@ public class EndpointMvcIntegrationTests {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class, @Import({ ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
JacksonAutoConfiguration.class, ErrorMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class }) ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration { protected @interface MinimalWebConfiguration {
} }
......
/*
* 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;
}
}
/*
* 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);
}
}
/**
* Return the delegate validator.
* @return the delegate validator
*/
protected final Validator getDelegate() {
return this.delegate;
}
}
/*
* 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);
}
}
/*
* 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.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
* @since 2.0.0
*/
public class SpringValidator implements SmartValidator, ApplicationContextAware,
InitializingBean, DisposableBean {
private final SpringValidatorAdapter target;
private final boolean existingBean;
public SpringValidator(SpringValidatorAdapter target, boolean existingBean) {
this.target = target;
this.existingBean = existingBean;
}
public final 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 SpringValidator((SpringValidatorAdapter) validator,
existingBean);
}
return new SpringValidator(
new SpringValidatorAdapter((javax.validation.Validator) validator),
existingBean);
}
return validator;
}
}
...@@ -19,18 +19,16 @@ package org.springframework.boot.autoconfigure.validation; ...@@ -19,18 +19,16 @@ package org.springframework.boot.autoconfigure.validation;
import javax.validation.Validator; import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator; import javax.validation.executable.ExecutableValidator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.core.env.Environment;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
/** /**
...@@ -43,19 +41,12 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess ...@@ -43,19 +41,12 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
@Configuration @Configuration
@ConditionalOnClass(ExecutableValidator.class) @ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider") @ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import({ DefaultValidatorConfiguration.class,
Jsr303ValidatorAdapterConfiguration.class })
public class ValidationAutoConfiguration { public class ValidationAutoConfiguration {
@Bean @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnBean(Validator.class)
@ConditionalOnMissingBean
public static Validator jsr303Validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
@Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor( public static MethodValidationPostProcessor methodValidationPostProcessor(
Environment environment, Validator validator) { Environment environment, Validator validator) {
......
...@@ -23,30 +23,50 @@ import java.util.concurrent.TimeUnit; ...@@ -23,30 +23,50 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
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.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; 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.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.validation.SpringValidator; import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain; import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean; 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.Configuration;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.format.Formatter; import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry; import org.springframework.format.FormatterRegistry;
import org.springframework.http.CacheControl; import org.springframework.http.CacheControl;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.EnableWebFlux;
...@@ -82,6 +102,12 @@ import org.springframework.web.reactive.result.view.ViewResolver; ...@@ -82,6 +102,12 @@ import org.springframework.web.reactive.result.view.ViewResolver;
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAnnotationAutoConfiguration { public class WebFluxAnnotationAutoConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public static WebFluxValidatorPostProcessor mvcValidatorAliasPostProcessor() {
return new WebFluxValidatorPostProcessor();
}
@Configuration @Configuration
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class }) @EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
@Import(EnableWebFluxConfiguration.class) @Import(EnableWebFluxConfiguration.class)
...@@ -190,17 +216,29 @@ public class WebFluxAnnotationAutoConfiguration { ...@@ -190,17 +216,29 @@ public class WebFluxAnnotationAutoConfiguration {
* Configuration equivalent to {@code @EnableWebFlux}. * Configuration equivalent to {@code @EnableWebFlux}.
*/ */
@Configuration @Configuration
public static class EnableWebFluxConfiguration public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration
extends DelegatingWebFluxConfiguration { implements InitializingBean {
private final ApplicationContext context;
public EnableWebFluxConfiguration(ApplicationContext context) {
this.context = context;
}
@Override
@Bean @Bean
@Override
@Conditional(DisableWebFluxValidatorCondition.class)
public Validator webFluxValidator() { public Validator webFluxValidator() {
if (!ClassUtils.isPresent("javax.validation.Validator", return this.context.getBean("webFluxValidator", Validator.class);
getClass().getClassLoader())) { }
return super.webFluxValidator();
} @Override
return SpringValidator.get(getApplicationContext(), getValidator()); public void afterPropertiesSet() throws Exception {
Assert.state(getValidator() == null,
"Found unexpected validator configuration. A Spring Boot WebFlux "
+ "validator should be registered as bean named "
+ "'webFluxValidator' and not returned from "
+ "WebFluxConfigurer.getValidator()");
} }
} }
...@@ -266,4 +304,128 @@ public class WebFluxAnnotationAutoConfiguration { ...@@ -266,4 +304,128 @@ public class WebFluxAnnotationAutoConfiguration {
} }
/**
* Condition used to disable the default WebFlux validator registration. The
* {@link WebFluxValidatorPostProcessor} is used to configure the
* {@code webFluxValidator} bean.
*/
static class DisableWebFluxValidatorCondition 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:
* <ul>
* <li>With no validators - Uses standard
* {@link WebFluxConfigurationSupport#webFluxValidator()} logic.</li>
* <li>With a single validator - Uses an alias.</li>
* <li>With multiple validators - Registers a mvcValidator bean if not already
* defined.</li>
* </ul>
*/
@Order(Ordered.LOWEST_PRECEDENCE)
static class WebFluxValidatorPostProcessor
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], "webFluxValidator");
}
else {
if (!ObjectUtils.containsElement(validatorBeans, "webFluxValidator")) {
registerMvcValidator(registry, beanFactory);
}
}
}
private void registerMvcValidator(BeanDefinitionRegistry registry,
ListableBeanFactory beanFactory) {
RootBeanDefinition definition = new RootBeanDefinition();
definition.setBeanClass(getClass());
definition.setFactoryMethodName("webFluxValidator");
registry.registerBeanDefinition("webFluxValidator", definition);
}
static Validator webFluxValidator() {
Validator validator = new WebFluxConfigurationSupport().webFluxValidator();
try {
if (ClassUtils.forName(JSR303_VALIDATOR_CLASS, null)
.isInstance(validator)) {
return new DelegatingWebFluxValidator(validator);
}
}
catch (Exception ex) {
}
return validator;
}
}
/**
* {@link DelegatingValidator} for the WebFlux validator.
*/
static class DelegatingWebFluxValidator extends DelegatingValidator
implements ApplicationContextAware, InitializingBean, DisposableBean {
DelegatingWebFluxValidator(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();
}
}
}
} }
...@@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
...@@ -322,9 +323,9 @@ public class SpringBootWebSecurityConfigurationTests { ...@@ -322,9 +323,9 @@ public class SpringBootWebSecurityConfigurationTests {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class, @Import({ ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class }) ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration { protected @interface MinimalWebConfiguration {
} }
......
/*
* 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);
}
}
/*
* 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 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.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringValidator}.
*
* @author Stephane Nicoll
*/
public class SpringValidatorTests {
private AnnotationConfigApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void wrapLocalValidatorFactoryBean() {
SpringValidator wrapper = load(LocalValidatorFactoryBeanConfig.class);
assertThat(wrapper.supports(SampleData.class)).isTrue();
MapBindingResult errors = new MapBindingResult(new HashMap<String, Object>(),
"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 SpringValidator load(Class<?> config) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(config);
ctx.refresh();
this.context = ctx;
return this.context.getBean(SpringValidator.class);
}
@Configuration
static class LocalValidatorFactoryBeanConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
@Bean
public SpringValidator wrapper() {
return new SpringValidator(validator(), true);
}
}
@Configuration
static class NonManagedBeanConfig {
private final LocalValidatorFactoryBean validator = mock(
LocalValidatorFactoryBean.class);
@Bean
public SpringValidator wrapper() {
return new SpringValidator(this.validator, false);
}
}
@Configuration
static class ManagedBeanConfig {
private final LocalValidatorFactoryBean validator = mock(
LocalValidatorFactoryBean.class);
@Bean
public SpringValidator wrapper() {
return new SpringValidator(this.validator, true);
}
}
static class SampleData {
@Min(42)
private int counter;
SampleData(int counter) {
this.counter = counter;
}
}
}
...@@ -32,9 +32,12 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext ...@@ -32,9 +32,12 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link ValidationAutoConfiguration}. * Tests for {@link ValidationAutoConfiguration}.
...@@ -56,45 +59,94 @@ public class ValidationAutoConfigurationTests { ...@@ -56,45 +59,94 @@ public class ValidationAutoConfigurationTests {
} }
@Test @Test
public void validationIsEnabled() { public void validationAutoConfigurationShouldConfigureJsrAndSpringValidator()
load(SampleService.class); 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 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 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); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
SampleService service = this.context.getBean(SampleService.class); ClassWithConstraint service = this.context.getBean(ClassWithConstraint.class);
service.doSomething("Valid"); service.call("Valid");
this.thrown.expect(ConstraintViolationException.class); this.thrown.expect(ConstraintViolationException.class);
service.doSomething("KO"); service.call("KO");
} }
@Test @Test
public void validationUsesCglibProxy() { public void validationAutoConfigurationShouldUseCglibProxy() {
load(DefaultAnotherSampleService.class); load(ImplementationOfInterfaceWithConstraint.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
DefaultAnotherSampleService service = this.context ImplementationOfInterfaceWithConstraint service = this.context
.getBean(DefaultAnotherSampleService.class); .getBean(ImplementationOfInterfaceWithConstraint.class);
service.doSomething(42); service.call(42);
this.thrown.expect(ConstraintViolationException.class); this.thrown.expect(ConstraintViolationException.class);
service.doSomething(2); service.call(2);
} }
@Test @Test
public void validationCanBeConfiguredToUseJdkProxy() { public void validationAutoConfigurationWhenProxyTargetClassIsFalseShouldUseJdkProxy() {
load(AnotherSampleServiceConfiguration.class, load(AnotherSampleServiceConfiguration.class,
"spring.aop.proxy-target-class=false"); "spring.aop.proxy-target-class=false");
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
assertThat(this.context.getBeansOfType(DefaultAnotherSampleService.class)) assertThat(this.context
.isEmpty(); .getBeansOfType(ImplementationOfInterfaceWithConstraint.class)).isEmpty();
AnotherSampleService service = this.context.getBean(AnotherSampleService.class); InterfaceWithConstraint service = this.context
service.doSomething(42); .getBean(InterfaceWithConstraint.class);
service.call(42);
this.thrown.expect(ConstraintViolationException.class); this.thrown.expect(ConstraintViolationException.class);
service.doSomething(2); service.call(2);
} }
@Test @Test
public void userDefinedMethodValidationPostProcessorTakesPrecedence() { public void validationAutoConfigurationWhenUserDefinesMethodValidationPostProcessorShouldBackOff() {
load(SampleConfiguration.class); load(UserDefinedMethodValidationConfig.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1); assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Object userMethodValidationPostProcessor = this.context Object userMethodValidationPostProcessor = this.context
.getBean("testMethodValidationPostProcessor"); .getBean("customMethodValidationPostProcessor");
assertThat(this.context.getBean(MethodValidationPostProcessor.class)) assertThat(this.context.getBean(MethodValidationPostProcessor.class))
.isSameAs(userMethodValidationPostProcessor); .isSameAs(userMethodValidationPostProcessor);
assertThat(this.context.getBeansOfType(MethodValidationPostProcessor.class)) assertThat(this.context.getBeansOfType(MethodValidationPostProcessor.class))
...@@ -115,47 +167,73 @@ public class ValidationAutoConfigurationTests { ...@@ -115,47 +167,73 @@ public class ValidationAutoConfigurationTests {
this.context = ctx; this.context = ctx;
} }
@Validated @Configuration
static class SampleService { 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 @Configuration
static class DefaultAnotherSampleService implements AnotherSampleService { static class UserDefinedMethodValidationConfig {
@Override
public void doSomething(Integer counter) {
@Bean
public MethodValidationPostProcessor customMethodValidationPostProcessor() {
return new MethodValidationPostProcessor();
} }
} }
@Configuration @Configuration
static class AnotherSampleServiceConfiguration { static class AnotherSampleServiceConfiguration {
@Bean @Bean
public AnotherSampleService anotherSampleService() { public InterfaceWithConstraint implementationOfInterfaceWithConstraint() {
return new DefaultAnotherSampleService(); return new ImplementationOfInterfaceWithConstraint();
} }
} }
@Configuration @Validated
static class SampleConfiguration { 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) {
}
}
} }
...@@ -36,6 +36,7 @@ import org.junit.rules.ExpectedException; ...@@ -36,6 +36,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
...@@ -129,9 +130,9 @@ public class BasicErrorControllerDirectMockMvcTests { ...@@ -129,9 +130,9 @@ public class BasicErrorControllerDirectMockMvcTests {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class, @Import({ ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class }) ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration { protected @interface MinimalWebConfiguration {
} }
......
...@@ -36,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -36,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
...@@ -131,9 +132,9 @@ public class BasicErrorControllerMockMvcTests { ...@@ -131,9 +132,9 @@ public class BasicErrorControllerMockMvcTests {
@Documented @Documented
@Import({ ServletWebServerFactoryAutoConfiguration.EmbeddedTomcat.class, @Import({ ServletWebServerFactoryAutoConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class }) ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
private @interface MinimalWebConfiguration { private @interface MinimalWebConfiguration {
} }
......
...@@ -92,4 +92,9 @@ public class OptionSetGroovyCompilerConfiguration implements GroovyCompilerConfi ...@@ -92,4 +92,9 @@ public class OptionSetGroovyCompilerConfiguration implements GroovyCompilerConfi
return this.repositoryConfiguration; return this.repositoryConfiguration;
} }
@Override
public boolean isQuiet() {
return false;
}
} }
...@@ -140,7 +140,7 @@ public class RunCommand extends OptionParsingCommand { ...@@ -140,7 +140,7 @@ public class RunCommand extends OptionParsingCommand {
@Override @Override
public Level getLogLevel() { public Level getLogLevel() {
if (getOptions().has(RunOptionHandler.this.quietOption)) { if (isQuiet()) {
return Level.OFF; return Level.OFF;
} }
if (getOptions().has(RunOptionHandler.this.verboseOption)) { if (getOptions().has(RunOptionHandler.this.verboseOption)) {
...@@ -149,6 +149,11 @@ public class RunCommand extends OptionParsingCommand { ...@@ -149,6 +149,11 @@ public class RunCommand extends OptionParsingCommand {
return Level.INFO; return Level.INFO;
} }
@Override
public boolean isQuiet() {
return getOptions().has(RunOptionHandler.this.quietOption);
}
} }
} }
......
...@@ -95,7 +95,8 @@ public class GroovyCompiler { ...@@ -95,7 +95,8 @@ public class GroovyCompiler {
new SpringBootDependenciesDependencyManagement()); new SpringBootDependenciesDependencyManagement());
AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(this.loader, AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(this.loader,
configuration.getRepositoryConfiguration(), resolutionContext); configuration.getRepositoryConfiguration(), resolutionContext,
configuration.isQuiet());
GrapeEngineInstaller.install(grapeEngine); GrapeEngineInstaller.install(grapeEngine);
......
...@@ -71,4 +71,10 @@ public interface GroovyCompilerConfiguration { ...@@ -71,4 +71,10 @@ public interface GroovyCompilerConfiguration {
*/ */
List<RepositoryConfiguration> getRepositoryConfiguration(); List<RepositoryConfiguration> getRepositoryConfiguration();
/**
* Returns if running in quiet mode.
* @return {@code true} if running in quiet mode
*/
boolean isQuiet();
} }
...@@ -77,7 +77,7 @@ public class AetherGrapeEngine implements GrapeEngine { ...@@ -77,7 +77,7 @@ public class AetherGrapeEngine implements GrapeEngine {
RepositorySystem repositorySystem, RepositorySystem repositorySystem,
DefaultRepositorySystemSession repositorySystemSession, DefaultRepositorySystemSession repositorySystemSession,
List<RemoteRepository> remoteRepositories, List<RemoteRepository> remoteRepositories,
DependencyResolutionContext resolutionContext) { DependencyResolutionContext resolutionContext, boolean quiet) {
this.classLoader = classLoader; this.classLoader = classLoader;
this.repositorySystem = repositorySystem; this.repositorySystem = repositorySystem;
this.session = repositorySystemSession; this.session = repositorySystemSession;
...@@ -88,12 +88,14 @@ public class AetherGrapeEngine implements GrapeEngine { ...@@ -88,12 +88,14 @@ public class AetherGrapeEngine implements GrapeEngine {
for (RemoteRepository repository : remotes) { for (RemoteRepository repository : remotes) {
addRepository(repository); addRepository(repository);
} }
this.progressReporter = getProgressReporter(this.session); this.progressReporter = getProgressReporter(this.session, quiet);
} }
private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session) { private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session,
String progressReporter = System.getProperty( boolean quiet) {
"org.springframework.boot.cli.compiler.grape.ProgressReporter"); String progressReporter = (quiet ? "none"
: System.getProperty(
"org.springframework.boot.cli.compiler.grape.ProgressReporter"));
if ("detail".equals(progressReporter) if ("detail".equals(progressReporter)
|| Boolean.getBoolean("groovy.grape.report.downloads")) { || Boolean.getBoolean("groovy.grape.report.downloads")) {
return new DetailedProgressReporter(session, System.out); return new DetailedProgressReporter(session, System.out);
......
...@@ -44,27 +44,21 @@ public abstract class AetherGrapeEngineFactory { ...@@ -44,27 +44,21 @@ public abstract class AetherGrapeEngineFactory {
public static AetherGrapeEngine create(GroovyClassLoader classLoader, public static AetherGrapeEngine create(GroovyClassLoader classLoader,
List<RepositoryConfiguration> repositoryConfigurations, List<RepositoryConfiguration> repositoryConfigurations,
DependencyResolutionContext dependencyResolutionContext) { DependencyResolutionContext dependencyResolutionContext, boolean quiet) {
RepositorySystem repositorySystem = createServiceLocator() RepositorySystem repositorySystem = createServiceLocator()
.getService(RepositorySystem.class); .getService(RepositorySystem.class);
DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils
.newSession(); .newSession();
ServiceLoader<RepositorySystemSessionAutoConfiguration> autoConfigurations = ServiceLoader ServiceLoader<RepositorySystemSessionAutoConfiguration> autoConfigurations = ServiceLoader
.load(RepositorySystemSessionAutoConfiguration.class); .load(RepositorySystemSessionAutoConfiguration.class);
for (RepositorySystemSessionAutoConfiguration autoConfiguration : autoConfigurations) { for (RepositorySystemSessionAutoConfiguration autoConfiguration : autoConfigurations) {
autoConfiguration.apply(repositorySystemSession, repositorySystem); autoConfiguration.apply(repositorySystemSession, repositorySystem);
} }
new DefaultRepositorySystemSessionAutoConfiguration() new DefaultRepositorySystemSessionAutoConfiguration()
.apply(repositorySystemSession, repositorySystem); .apply(repositorySystemSession, repositorySystem);
return new AetherGrapeEngine(classLoader, repositorySystem, return new AetherGrapeEngine(classLoader, repositorySystem,
repositorySystemSession, createRepositories(repositoryConfigurations), repositorySystemSession, createRepositories(repositoryConfigurations),
dependencyResolutionContext); dependencyResolutionContext, quiet);
} }
private static ServiceLocator createServiceLocator() { private static ServiceLocator createServiceLocator() {
......
...@@ -81,6 +81,11 @@ public class GroovyGrabDependencyResolverTests { ...@@ -81,6 +81,11 @@ public class GroovyGrabDependencyResolverTests {
return new String[] { "." }; return new String[] { "." };
} }
@Override
public boolean isQuiet() {
return false;
}
}; };
this.resolver = new GroovyGrabDependencyResolver(configuration); this.resolver = new GroovyGrabDependencyResolver(configuration);
} }
......
...@@ -59,7 +59,7 @@ public class AetherGrapeEngineTests { ...@@ -59,7 +59,7 @@ public class AetherGrapeEngineTests {
dependencyResolutionContext.addDependencyManagement( dependencyResolutionContext.addDependencyManagement(
new SpringBootDependenciesDependencyManagement()); new SpringBootDependenciesDependencyManagement());
return AetherGrapeEngineFactory.create(this.groovyClassLoader, return AetherGrapeEngineFactory.create(this.groovyClassLoader,
repositoryConfigurations, dependencyResolutionContext); repositoryConfigurations, dependencyResolutionContext, false);
} }
@Test @Test
......
...@@ -283,6 +283,8 @@ the `Main-Class` attribute and leave out `Start-Class`. ...@@ -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), * `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 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). 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 * `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 nested one if running from an archive). Because of this `PropertiesLauncher` behaves the
same as `JarLauncher` when no additional configuration is provided. same as `JarLauncher` when no additional configuration is provided.
......
...@@ -250,8 +250,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr ...@@ -250,8 +250,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
protected Class<?>[] getOrFindConfigurationClasses( protected Class<?>[] getOrFindConfigurationClasses(
MergedContextConfiguration mergedConfig) { MergedContextConfiguration mergedConfig) {
Class<?>[] classes = mergedConfig.getClasses(); Class<?>[] classes = mergedConfig.getClasses();
if (containsNonTestComponent(classes) || mergedConfig.hasLocations() if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) {
|| !mergedConfig.getContextInitializerClasses().isEmpty()) {
return classes; return classes;
} }
Class<?> found = new SpringBootConfigurationFinder() Class<?> found = new SpringBootConfigurationFinder()
......
/*
* 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<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
}
}
}
...@@ -25,8 +25,10 @@ import java.net.URL; ...@@ -25,8 +25,10 @@ import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
...@@ -299,11 +301,9 @@ public class PropertiesLauncher extends Launcher { ...@@ -299,11 +301,9 @@ public class PropertiesLauncher extends Launcher {
List<String> paths = new ArrayList<>(); List<String> paths = new ArrayList<>();
for (String path : commaSeparatedPaths.split(",")) { for (String path : commaSeparatedPaths.split(",")) {
path = cleanupPath(path); path = cleanupPath(path);
// Empty path (i.e. the archive itself if running from a JAR) is always added // "" means the user wants root of archive but not current directory
// to the classpath so no need for it to be explicitly listed path = ("".equals(path) ? "/" : path);
if (!path.equals("")) { paths.add(path);
paths.add(path);
}
} }
if (paths.isEmpty()) { if (paths.isEmpty()) {
paths.add("lib"); paths.add("lib");
...@@ -336,7 +336,13 @@ public class PropertiesLauncher extends Launcher { ...@@ -336,7 +336,13 @@ public class PropertiesLauncher extends Launcher {
@Override @Override
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception { protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
ClassLoader loader = super.createClassLoader(archives); Set<URL> urls = new LinkedHashSet<URL>(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"); String customLoaderClassName = getProperty("loader.classLoader");
if (customLoaderClassName != null) { if (customLoaderClassName != null) {
loader = wrapWithCustomClassLoader(loader, customLoaderClassName); loader = wrapWithCustomClassLoader(loader, customLoaderClassName);
...@@ -454,13 +460,15 @@ public class PropertiesLauncher extends Launcher { ...@@ -454,13 +460,15 @@ public class PropertiesLauncher extends Launcher {
String root = cleanupPath(stripFileUrlPrefix(path)); String root = cleanupPath(stripFileUrlPrefix(path));
List<Archive> lib = new ArrayList<>(); List<Archive> lib = new ArrayList<>();
File file = new File(root); File file = new File(root);
if (!isAbsolutePath(root)) { if (!"/".equals(root)) {
file = new File(this.home, root); if (!isAbsolutePath(root)) {
} file = new File(this.home, root);
if (file.isDirectory()) { }
debug("Adding classpath entries from " + file); if (file.isDirectory()) {
Archive archive = new ExplodedArchive(file, false); debug("Adding classpath entries from " + file);
lib.add(archive); Archive archive = new ExplodedArchive(file, false);
lib.add(archive);
}
} }
Archive archive = getArchive(file); Archive archive = getArchive(file);
if (archive != null) { if (archive != null) {
...@@ -488,24 +496,46 @@ public class PropertiesLauncher extends Launcher { ...@@ -488,24 +496,46 @@ public class PropertiesLauncher extends Launcher {
return null; return null;
} }
private List<Archive> getNestedArchives(String root) throws Exception { private List<Archive> getNestedArchives(String path) throws Exception {
if (root.startsWith("/") Archive parent = this.parent;
|| this.parent.getUrl().equals(this.home.toURI().toURL())) { 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. // If home dir is same as parent archive, no need to add it twice.
return null; return null;
} }
Archive parent = this.parent; if (root.contains("!")) {
if (root.startsWith("jar:file:") && root.contains("!")) {
int index = root.indexOf("!"); int index = root.indexOf("!");
String file = root.substring("jar:file:".length(), index); File file = new File(this.home, root.substring(0, index));
parent = new JarFileArchive(new File(file)); 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()); root = root.substring(index + 1, root.length());
while (root.startsWith("/")) { while (root.startsWith("/")) {
root = root.substring(1); 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); EntryFilter filter = new PrefixMatchingArchiveFilter(root);
return parent.getNestedArchives(filter); List<Archive> archives = new ArrayList<Archive>(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<Archive> lib) { private void addNestedEntries(List<Archive> lib) {
...@@ -518,7 +548,7 @@ public class PropertiesLauncher extends Launcher { ...@@ -518,7 +548,7 @@ public class PropertiesLauncher extends Launcher {
@Override @Override
public boolean matches(Entry entry) { public boolean matches(Entry entry) {
if (entry.isDirectory()) { 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); return entry.getName().startsWith(JarLauncher.BOOT_INF_LIB);
} }
...@@ -607,6 +637,9 @@ public class PropertiesLauncher extends Launcher { ...@@ -607,6 +637,9 @@ public class PropertiesLauncher extends Launcher {
@Override @Override
public boolean matches(Entry entry) { public boolean matches(Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(this.prefix);
}
return entry.getName().startsWith(this.prefix) && this.filter.matches(entry); return entry.getName().startsWith(this.prefix) && this.filter.matches(entry);
} }
......
...@@ -21,12 +21,13 @@ import java.io.FileOutputStream; ...@@ -21,12 +21,13 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import org.assertj.core.api.Condition;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
...@@ -36,6 +37,9 @@ import org.junit.rules.TemporaryFolder; ...@@ -36,6 +37,9 @@ import org.junit.rules.TemporaryFolder;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.boot.loader.archive.Archive; 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 org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -72,6 +76,7 @@ public class PropertiesLauncherTests { ...@@ -72,6 +76,7 @@ public class PropertiesLauncherTests {
System.clearProperty("loader.config.name"); System.clearProperty("loader.config.name");
System.clearProperty("loader.config.location"); System.clearProperty("loader.config.location");
System.clearProperty("loader.system"); System.clearProperty("loader.system");
System.clearProperty("loader.classLoader");
} }
@Test @Test
...@@ -131,6 +136,16 @@ public class PropertiesLauncherTests { ...@@ -131,6 +136,16 @@ public class PropertiesLauncherTests {
.isEqualTo("[.]"); .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<Archive> archives = launcher.getClassPathArchives();
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
}
@Test @Test
public void testUserSpecifiedWildcardPath() throws Exception { public void testUserSpecifiedWildcardPath() throws Exception {
System.setProperty("loader.path", "jars/*"); System.setProperty("loader.path", "jars/*");
...@@ -153,13 +168,44 @@ public class PropertiesLauncherTests { ...@@ -153,13 +168,44 @@ public class PropertiesLauncherTests {
waitFor("Hello World"); 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<Archive> 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<Archive> 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<Archive> archives = launcher.getClassPathArchives();
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
}
@Test @Test
public void testUserSpecifiedJarFileWithNestedArchives() throws Exception { public void testUserSpecifiedJarFileWithNestedArchives() throws Exception {
System.setProperty("loader.path", "nested-jars/app.jar"); System.setProperty("loader.path", "nested-jars/app.jar");
System.setProperty("loader.main", "demo.Application"); System.setProperty("loader.main", "demo.Application");
PropertiesLauncher launcher = new PropertiesLauncher(); PropertiesLauncher launcher = new PropertiesLauncher();
launcher.launch(new String[0]); List<Archive> archives = launcher.getClassPathArchives();
waitFor("Hello World"); assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
} }
@Test @Test
...@@ -209,11 +255,28 @@ public class PropertiesLauncherTests { ...@@ -209,11 +255,28 @@ public class PropertiesLauncherTests {
public void testCustomClassLoaderCreation() throws Exception { public void testCustomClassLoaderCreation() throws Exception {
System.setProperty("loader.classLoader", TestLoader.class.getName()); System.setProperty("loader.classLoader", TestLoader.class.getName());
PropertiesLauncher launcher = new PropertiesLauncher(); PropertiesLauncher launcher = new PropertiesLauncher();
ClassLoader loader = launcher.createClassLoader(Collections.<Archive>emptyList()); ClassLoader loader = launcher.createClassLoader(archives());
assertThat(loader).isNotNull(); assertThat(loader).isNotNull();
assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName()); assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName());
} }
private List<Archive> archives() throws Exception {
List<Archive> archives = new ArrayList<Archive>();
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 @Test
public void testUserSpecifiedConfigPathWins() throws Exception { public void testUserSpecifiedConfigPathWins() throws Exception {
...@@ -280,6 +343,17 @@ public class PropertiesLauncherTests { ...@@ -280,6 +343,17 @@ public class PropertiesLauncherTests {
assertThat(timeout).as("Timed out waiting for (" + value + ")").isTrue(); assertThat(timeout).as("Timed out waiting for (" + value + ")").isTrue();
} }
private Condition<Archive> endingWith(final String value) {
return new Condition<Archive>() {
@Override
public boolean matches(Archive archive) {
return archive.toString().endsWith(value);
}
};
}
public static class TestLoader extends URLClassLoader { public static class TestLoader extends URLClassLoader {
public TestLoader(ClassLoader parent) { public TestLoader(ClassLoader parent) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment