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
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
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.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
......@@ -85,15 +86,16 @@ public class EndpointMvcIntegrationTests {
@Test
public void envEndpointNotHidden() throws InterruptedException {
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(this.interceptor.invoked()).isTrue();
}
@Test
public void healthEndpointNotHidden() throws InterruptedException {
String body = new TestRestTemplate()
.getForObject("http://localhost:" + this.port + "/application/health", String.class);
String body = new TestRestTemplate().getForObject(
"http://localhost:" + this.port + "/application/health", String.class);
assertThat(body).isNotNull().contains("status");
assertThat(this.interceptor.invoked()).isTrue();
}
......@@ -153,9 +155,9 @@ public class EndpointMvcIntegrationTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.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 {
}
......
/*
* 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;
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) {
......
......@@ -23,30 +23,50 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
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.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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.validation.SpringValidator;
import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
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.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.Role;
import org.springframework.core.Ordered;
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.GenericConverter;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.CacheControl;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
import org.springframework.web.reactive.config.EnableWebFlux;
......@@ -82,6 +102,12 @@ import org.springframework.web.reactive.result.view.ViewResolver;
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAnnotationAutoConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public static WebFluxValidatorPostProcessor mvcValidatorAliasPostProcessor() {
return new WebFluxValidatorPostProcessor();
}
@Configuration
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
@Import(EnableWebFluxConfiguration.class)
......@@ -190,17 +216,29 @@ public class WebFluxAnnotationAutoConfiguration {
* Configuration equivalent to {@code @EnableWebFlux}.
*/
@Configuration
public static class EnableWebFluxConfiguration
extends DelegatingWebFluxConfiguration {
public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration
implements InitializingBean {
private final ApplicationContext context;
public EnableWebFluxConfiguration(ApplicationContext context) {
this.context = context;
}
@Override
@Bean
@Override
@Conditional(DisableWebFluxValidatorCondition.class)
public Validator webFluxValidator() {
if (!ClassUtils.isPresent("javax.validation.Validator",
getClass().getClassLoader())) {
return super.webFluxValidator();
}
return SpringValidator.get(getApplicationContext(), getValidator());
return this.context.getBean("webFluxValidator", Validator.class);
}
@Override
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 {
}
/**
* 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;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
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.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
......@@ -322,9 +323,9 @@ public class SpringBootWebSecurityConfigurationTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.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 {
}
......
/*
* 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
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);
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 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);
SampleService service = this.context.getBean(SampleService.class);
service.doSomething("Valid");
ClassWithConstraint service = this.context.getBean(ClassWithConstraint.class);
service.call("Valid");
this.thrown.expect(ConstraintViolationException.class);
service.doSomething("KO");
service.call("KO");
}
@Test
public void validationUsesCglibProxy() {
load(DefaultAnotherSampleService.class);
public void validationAutoConfigurationShouldUseCglibProxy() {
load(ImplementationOfInterfaceWithConstraint.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
DefaultAnotherSampleService service = this.context
.getBean(DefaultAnotherSampleService.class);
service.doSomething(42);
ImplementationOfInterfaceWithConstraint service = this.context
.getBean(ImplementationOfInterfaceWithConstraint.class);
service.call(42);
this.thrown.expect(ConstraintViolationException.class);
service.doSomething(2);
service.call(2);
}
@Test
public void validationCanBeConfiguredToUseJdkProxy() {
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) {
}
}
}
......@@ -36,6 +36,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
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.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
......@@ -129,9 +130,9 @@ public class BasicErrorControllerDirectMockMvcTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.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 {
}
......
......@@ -36,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
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.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
......@@ -131,9 +132,9 @@ public class BasicErrorControllerMockMvcTests {
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
private @interface MinimalWebConfiguration {
}
......
......@@ -92,4 +92,9 @@ public class OptionSetGroovyCompilerConfiguration implements GroovyCompilerConfi
return this.repositoryConfiguration;
}
@Override
public boolean isQuiet() {
return false;
}
}
......@@ -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);
}
}
}
......
......@@ -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);
......
......@@ -71,4 +71,10 @@ public interface GroovyCompilerConfiguration {
*/
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 {
RepositorySystem repositorySystem,
DefaultRepositorySystemSession repositorySystemSession,
List<RemoteRepository> remoteRepositories,
DependencyResolutionContext resolutionContext) {
DependencyResolutionContext resolutionContext, boolean quiet) {
this.classLoader = classLoader;
this.repositorySystem = repositorySystem;
this.session = repositorySystemSession;
......@@ -88,12 +88,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);
......
......@@ -44,27 +44,21 @@ public abstract class AetherGrapeEngineFactory {
public static AetherGrapeEngine create(GroovyClassLoader classLoader,
List<RepositoryConfiguration> repositoryConfigurations,
DependencyResolutionContext dependencyResolutionContext) {
DependencyResolutionContext dependencyResolutionContext, boolean quiet) {
RepositorySystem repositorySystem = createServiceLocator()
.getService(RepositorySystem.class);
DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils
.newSession();
ServiceLoader<RepositorySystemSessionAutoConfiguration> 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() {
......
......@@ -81,6 +81,11 @@ public class GroovyGrabDependencyResolverTests {
return new String[] { "." };
}
@Override
public boolean isQuiet() {
return false;
}
};
this.resolver = new GroovyGrabDependencyResolver(configuration);
}
......
......@@ -59,7 +59,7 @@ public class AetherGrapeEngineTests {
dependencyResolutionContext.addDependencyManagement(
new SpringBootDependenciesDependencyManagement());
return AetherGrapeEngineFactory.create(this.groovyClassLoader,
repositoryConfigurations, dependencyResolutionContext);
repositoryConfigurations, dependencyResolutionContext, false);
}
@Test
......
......@@ -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.
......
......@@ -250,8 +250,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()
......
/*
* 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;
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<String> 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<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");
if (customLoaderClassName != null) {
loader = wrapWithCustomClassLoader(loader, customLoaderClassName);
......@@ -454,13 +460,15 @@ public class PropertiesLauncher extends Launcher {
String root = cleanupPath(stripFileUrlPrefix(path));
List<Archive> 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<Archive> getNestedArchives(String root) throws Exception {
if (root.startsWith("/")
|| this.parent.getUrl().equals(this.home.toURI().toURL())) {
private List<Archive> 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<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) {
......@@ -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);
}
......
......@@ -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<Archive> 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<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
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<Archive> 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.<Archive>emptyList());
ClassLoader loader = launcher.createClassLoader(archives());
assertThat(loader).isNotNull();
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
public void testUserSpecifiedConfigPathWins() throws Exception {
......@@ -280,6 +343,17 @@ public class PropertiesLauncherTests {
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 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