Add @PropertyMapping support
Add @PropertyMapping annotation which can be used to mark annotation attributes that should contribute Environment properties. Provides a quick way for tests to change auto-configuration behavior in a structured way. Fixes gh-4901
This commit is contained in:
parent
e5f224118b
commit
46099c753b
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
* 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.autoconfigure.properties;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.core.env.EnumerablePropertySource;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link EnumerablePropertySource} to adapt annotations marked with
|
||||||
|
* {@link PropertyMapping @PropertyMapping}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.4.0
|
||||||
|
*/
|
||||||
|
public class AnnotationsPropertySource extends EnumerablePropertySource<Class<?>> {
|
||||||
|
|
||||||
|
private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");
|
||||||
|
|
||||||
|
private final Map<String, Object> properties;
|
||||||
|
|
||||||
|
public AnnotationsPropertySource(Class<?> source) {
|
||||||
|
this("Annotations", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnnotationsPropertySource(String name, Class<?> source) {
|
||||||
|
super(name, source);
|
||||||
|
this.properties = getProperties(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getProperties(Class<?> source) {
|
||||||
|
Map<String, Object> properties = new LinkedHashMap<String, Object>();
|
||||||
|
collectProperties(source, properties);
|
||||||
|
return Collections.unmodifiableMap(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectProperties(Class<?> source, Map<String, Object> properties) {
|
||||||
|
if (source != null) {
|
||||||
|
for (Annotation annotation : source.getDeclaredAnnotations()) {
|
||||||
|
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
|
||||||
|
PropertyMapping typeMapping = AnnotationUtils.getAnnotation(
|
||||||
|
annotation.annotationType(), PropertyMapping.class);
|
||||||
|
for (Method attribute : annotation.annotationType()
|
||||||
|
.getDeclaredMethods()) {
|
||||||
|
collectProperties(annotation, attribute, typeMapping, properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collectProperties(source.getSuperclass(), properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectProperties(Annotation annotation, Method attribute,
|
||||||
|
PropertyMapping typeMapping, Map<String, Object> properties) {
|
||||||
|
PropertyMapping attributeMapping = AnnotationUtils.getAnnotation(attribute,
|
||||||
|
PropertyMapping.class);
|
||||||
|
if (isMapped(typeMapping, attributeMapping)) {
|
||||||
|
String name = getName(typeMapping, attributeMapping, attribute);
|
||||||
|
ReflectionUtils.makeAccessible(attribute);
|
||||||
|
Object value = ReflectionUtils.invokeMethod(attribute, annotation);
|
||||||
|
putProperties(name, value, properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMapped(PropertyMapping typeMapping,
|
||||||
|
PropertyMapping attributeMapping) {
|
||||||
|
if (attributeMapping != null) {
|
||||||
|
return attributeMapping.map();
|
||||||
|
}
|
||||||
|
return (typeMapping != null && typeMapping.map());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getName(PropertyMapping typeMapping, PropertyMapping attributeMapping,
|
||||||
|
Method attribute) {
|
||||||
|
String prefix = (typeMapping == null ? "" : typeMapping.value());
|
||||||
|
String name = (attributeMapping == null ? "" : attributeMapping.value());
|
||||||
|
if (!StringUtils.hasText(name)) {
|
||||||
|
name = toKebabCase(attribute.getName());
|
||||||
|
}
|
||||||
|
return dotAppend(prefix, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toKebabCase(String name) {
|
||||||
|
Matcher matcher = CAMEL_CASE_PATTERN.matcher(name);
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
while (matcher.find()) {
|
||||||
|
matcher.appendReplacement(result,
|
||||||
|
matcher.group(1) + '-' + StringUtils.uncapitalize(matcher.group(2)));
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
return result.toString().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String dotAppend(String prefix, String postfix) {
|
||||||
|
prefix = (prefix == null ? "" : prefix);
|
||||||
|
if (StringUtils.hasText(prefix)) {
|
||||||
|
return (prefix.endsWith(".") ? prefix + postfix : prefix + "." + postfix);
|
||||||
|
}
|
||||||
|
return postfix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putProperties(String name, Object value,
|
||||||
|
Map<String, Object> properties) {
|
||||||
|
if (ObjectUtils.isArray(value)) {
|
||||||
|
Object[] array = ObjectUtils.toObjectArray(value);
|
||||||
|
for (int i = 0; i < array.length; i++) {
|
||||||
|
properties.put(name + "[" + i + "]", array[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
properties.put(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsProperty(String name) {
|
||||||
|
return this.properties.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getProperty(String name) {
|
||||||
|
return this.properties.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getPropertyNames() {
|
||||||
|
return StringUtils.toStringArray(this.properties.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.properties.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.properties.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.properties.equals(((AnnotationsPropertySource) obj).properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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.autoconfigure.properties;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that attributes from a test annotation should be mapped into a
|
||||||
|
* {@link PropertySource}. Can be used at the type level, or on individual attributes. For
|
||||||
|
* example, the following annotation declaration: <pre class="code">
|
||||||
|
* @Retention(RUNTIME)
|
||||||
|
* @PropertyMapping("my.example")
|
||||||
|
* public @interface Example {
|
||||||
|
*
|
||||||
|
* String name();
|
||||||
|
*
|
||||||
|
* }
|
||||||
|
* </pre> When used on a test class as follows: <pre class="code">
|
||||||
|
* @Example(name="Spring")
|
||||||
|
* public class MyTest {
|
||||||
|
* }
|
||||||
|
* </pre> will result in a {@literal my.example.name} property being added with the value
|
||||||
|
* {@literal "Spring"}.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.4.0
|
||||||
|
* @see AnnotationsPropertySource
|
||||||
|
* @see TestPropertySource
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||||
|
public @interface PropertyMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the property mapping. When used at the type-level, this value will be used
|
||||||
|
* as a prefix for all mapped attributes. When used on an attribute, the value
|
||||||
|
* overrides the generated (kebab case) name.
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if mapping should occur. When specified at the type-level indicates if
|
||||||
|
* mapping should occur by default or not. When used at the attribute-level, overrides
|
||||||
|
* the type-level default.
|
||||||
|
*/
|
||||||
|
boolean map() default true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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.autoconfigure.properties;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.test.context.ContextCustomizer;
|
||||||
|
import org.springframework.test.context.MergedContextConfiguration;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ContextCustomizer} to map annotation attributes to {@link Environment}
|
||||||
|
* properties.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
class PropertyMappingContextCustomizer implements ContextCustomizer {
|
||||||
|
|
||||||
|
private final AnnotationsPropertySource propertySource;
|
||||||
|
|
||||||
|
PropertyMappingContextCustomizer(AnnotationsPropertySource propertySource) {
|
||||||
|
this.propertySource = propertySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customizeContext(ConfigurableApplicationContext context,
|
||||||
|
MergedContextConfiguration mergedContextConfiguration) {
|
||||||
|
if (!this.propertySource.isEmpty()) {
|
||||||
|
context.getEnvironment().getPropertySources().addFirst(this.propertySource);
|
||||||
|
}
|
||||||
|
context.getBeanFactory().registerSingleton(
|
||||||
|
PropertyMappingCheckBeanPostProcessor.class.getName(),
|
||||||
|
new PropertyMappingCheckBeanPostProcessor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.propertySource.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return (obj != null && getClass().equals(obj.getClass()) && this.propertySource
|
||||||
|
.equals(((PropertyMappingContextCustomizer) obj).propertySource));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link BeanPostProcessor} to check that {@link PropertyMapping} is only used on
|
||||||
|
* test classes.
|
||||||
|
*/
|
||||||
|
static class PropertyMappingCheckBeanPostProcessor implements BeanPostProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||||
|
throws BeansException {
|
||||||
|
Class<?> beanClass = bean.getClass();
|
||||||
|
boolean hasComponent = AnnotationUtils.findAnnotation(beanClass,
|
||||||
|
Component.class) != null;
|
||||||
|
boolean hasPropertyMapping = AnnotationUtils.findAnnotation(beanClass,
|
||||||
|
PropertyMapping.class) != null;
|
||||||
|
if (hasComponent) {
|
||||||
|
Assert.state(!hasPropertyMapping,
|
||||||
|
"@PropertyMapping annotations can only be used on test classes");
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||||
|
throws BeansException {
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* 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.autoconfigure.properties;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||||
|
import org.springframework.test.context.ContextCustomizer;
|
||||||
|
import org.springframework.test.context.ContextCustomizerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ContextCustomizerFactory} to map annotation attributes to {@link Environment}
|
||||||
|
* properties.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
class PropertyMappingContextCustomizerFactory implements ContextCustomizerFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContextCustomizer createContextCustomizer(Class<?> testClass,
|
||||||
|
List<ContextConfigurationAttributes> configurationAttributes) {
|
||||||
|
AnnotationsPropertySource propertySource = new AnnotationsPropertySource(
|
||||||
|
testClass);
|
||||||
|
return new PropertyMappingContextCustomizer(propertySource);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2016 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support for mapping annotation attribute values in the Spring {@code Environment}.
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.test.autoconfigure.properties;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# Spring Test ContextCustomizerFactories
|
# Spring Test ContextCustomizerFactories
|
||||||
org.springframework.test.context.ContextCustomizerFactory=\
|
org.springframework.test.context.ContextCustomizerFactory=\
|
||||||
org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory
|
org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory,\
|
||||||
|
org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizerFactory
|
||||||
|
|
||||||
# Test Execution Listeners
|
# Test Execution Listeners
|
||||||
org.springframework.test.context.TestExecutionListener=\
|
org.springframework.test.context.TestExecutionListener=\
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,265 @@
|
||||||
|
/*
|
||||||
|
* 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.autoconfigure.properties;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link AnnotationsPropertySource}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class AnnotationsPropertySourceTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createWhenSourceIsNullShouldThrowException() throws Exception {
|
||||||
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
|
this.thrown.expectMessage("Property source must not be null");
|
||||||
|
new AnnotationsPropertySource(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenHasNoAnnotationShouldBeEmpty() throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(
|
||||||
|
NoAnnotation.class);
|
||||||
|
assertThat(source.getPropertyNames()).isEmpty();
|
||||||
|
assertThat(source.getProperty("value")).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenHasTypeLevelAnnotationShouldUseAttributeName()
|
||||||
|
throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(TypeLevel.class);
|
||||||
|
assertThat(source.getPropertyNames()).containsExactly("value");
|
||||||
|
assertThat(source.getProperty("value")).isEqualTo("abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenHasTypeLevelWithPrefixShouldUsePrefixedName()
|
||||||
|
throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(
|
||||||
|
TypeLevelWithPrefix.class);
|
||||||
|
assertThat(source.getPropertyNames()).containsExactly("test.value");
|
||||||
|
assertThat(source.getProperty("test.value")).isEqualTo("abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenHasAttributeLevelWithPrefixShouldUsePrefixedName()
|
||||||
|
throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(
|
||||||
|
AttributeLevelWithPrefix.class);
|
||||||
|
assertThat(source.getPropertyNames()).containsExactly("test");
|
||||||
|
assertThat(source.getProperty("test")).isEqualTo("abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenHasTypeAndAttributeLevelWithPrefixShouldUsePrefixedName()
|
||||||
|
throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(
|
||||||
|
TypeAndAttributeLevelWithPrefix.class);
|
||||||
|
assertThat(source.getPropertyNames()).containsExactly("test.example");
|
||||||
|
assertThat(source.getProperty("test.example")).isEqualTo("abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenNotMappedAtTypeLevelShouldIgnoreAttributes()
|
||||||
|
throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(
|
||||||
|
NotMappedAtTypeLevel.class);
|
||||||
|
assertThat(source.getPropertyNames()).containsExactly("value");
|
||||||
|
assertThat(source.getProperty("ignore")).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenNotMappedAtAttributeLevelShouldIgnoreAttributes()
|
||||||
|
throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(
|
||||||
|
NotMappedAtAttributeLevel.class);
|
||||||
|
assertThat(source.getPropertyNames()).containsExactly("value");
|
||||||
|
assertThat(source.getProperty("ignore")).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenCountainsArryasShouldExpandNames() throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(Arrays.class);
|
||||||
|
assertThat(source.getPropertyNames()).contains("strings[0]", "strings[1]",
|
||||||
|
"classes[0]", "classes[1]", "ints[0]", "ints[1]", "longs[0]", "longs[1]",
|
||||||
|
"floats[0]", "floats[1]", "doubles[0]", "doubles[1]", "booleans[0]",
|
||||||
|
"booleans[1]");
|
||||||
|
assertThat(source.getProperty("strings[0]")).isEqualTo("a");
|
||||||
|
assertThat(source.getProperty("strings[1]")).isEqualTo("b");
|
||||||
|
assertThat(source.getProperty("classes[0]")).isEqualTo(Integer.class);
|
||||||
|
assertThat(source.getProperty("classes[1]")).isEqualTo(Long.class);
|
||||||
|
assertThat(source.getProperty("ints[0]")).isEqualTo(1);
|
||||||
|
assertThat(source.getProperty("ints[1]")).isEqualTo(2);
|
||||||
|
assertThat(source.getProperty("longs[0]")).isEqualTo(1L);
|
||||||
|
assertThat(source.getProperty("longs[1]")).isEqualTo(2L);
|
||||||
|
assertThat(source.getProperty("floats[0]")).isEqualTo(1.0f);
|
||||||
|
assertThat(source.getProperty("floats[1]")).isEqualTo(2.0f);
|
||||||
|
assertThat(source.getProperty("doubles[0]")).isEqualTo(1.0);
|
||||||
|
assertThat(source.getProperty("doubles[1]")).isEqualTo(2.0);
|
||||||
|
assertThat(source.getProperty("booleans[0]")).isEqualTo(false);
|
||||||
|
assertThat(source.getProperty("booleans[1]")).isEqualTo(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWhenHasCamelCaseShouldConvertToKebabCase() throws Exception {
|
||||||
|
AnnotationsPropertySource source = new AnnotationsPropertySource(
|
||||||
|
CamelCaseToKebabCase.class);
|
||||||
|
assertThat(source.getPropertyNames()).contains("camel-case-to-kebab-case");
|
||||||
|
}
|
||||||
|
|
||||||
|
static class NoAnnotation {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeLevelAnnotation("abc")
|
||||||
|
static class TypeLevel {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping
|
||||||
|
static @interface TypeLevelAnnotation {
|
||||||
|
|
||||||
|
String value();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeLevelWithPrefixAnnotation("abc")
|
||||||
|
static class TypeLevelWithPrefix {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping("test")
|
||||||
|
static @interface TypeLevelWithPrefixAnnotation {
|
||||||
|
|
||||||
|
String value();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AttributeLevelWithPrefixAnnotation("abc")
|
||||||
|
static class AttributeLevelWithPrefix {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AttributeLevelWithPrefixAnnotation {
|
||||||
|
|
||||||
|
@PropertyMapping("test")
|
||||||
|
String value();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeAndAttributeLevelWithPrefixAnnotation("abc")
|
||||||
|
static class TypeAndAttributeLevelWithPrefix {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping("test")
|
||||||
|
static @interface TypeAndAttributeLevelWithPrefixAnnotation {
|
||||||
|
|
||||||
|
@PropertyMapping("example")
|
||||||
|
String value();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotMappedAtTypeLevelAnnotation("abc")
|
||||||
|
static class NotMappedAtTypeLevel {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping(map = false)
|
||||||
|
static @interface NotMappedAtTypeLevelAnnotation {
|
||||||
|
|
||||||
|
@PropertyMapping
|
||||||
|
String value();
|
||||||
|
|
||||||
|
String ignore() default "xyz";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotMappedAtAttributeLevelAnnotation("abc")
|
||||||
|
static class NotMappedAtAttributeLevel {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping
|
||||||
|
static @interface NotMappedAtAttributeLevelAnnotation {
|
||||||
|
|
||||||
|
String value();
|
||||||
|
|
||||||
|
@PropertyMapping(map = false)
|
||||||
|
String ignore() default "xyz";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ArraysAnnotation(strings = { "a", "b" }, classes = { Integer.class,
|
||||||
|
Long.class }, ints = { 1, 2 }, longs = { 1, 2 }, floats = { 1.0f,
|
||||||
|
2.0f }, doubles = { 1.0, 2.0 }, booleans = { false, true })
|
||||||
|
static class Arrays {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping
|
||||||
|
static @interface ArraysAnnotation {
|
||||||
|
|
||||||
|
String[] strings();
|
||||||
|
|
||||||
|
Class<?>[] classes();
|
||||||
|
|
||||||
|
int[] ints();
|
||||||
|
|
||||||
|
long[] longs();
|
||||||
|
|
||||||
|
float[] floats();
|
||||||
|
|
||||||
|
double[] doubles();
|
||||||
|
|
||||||
|
boolean[] booleans();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@CamelCaseToKebabCaseAnnotation
|
||||||
|
static class CamelCaseToKebabCase {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping
|
||||||
|
static @interface CamelCaseToKebabCaseAnnotation {
|
||||||
|
|
||||||
|
String camelCaseToKebabCase() default "abc";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.autoconfigure.properties;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example {@link PropertyMapping} annotation for use wuth {@link PropertyMappingTests}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping
|
||||||
|
@interface ExampleMapping {
|
||||||
|
|
||||||
|
String exampleProperty();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* 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.autoconfigure.properties;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
|
import org.springframework.test.context.ContextCustomizer;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PropertyMappingContextCustomizerFactory}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class PropertyMappingContextCustomizerFactoryTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
private PropertyMappingContextCustomizerFactory factory = new PropertyMappingContextCustomizerFactory();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getContextCustomizerWhenHasNoMappingShouldNotAddPropertySource() {
|
||||||
|
ContextCustomizer customizer = this.factory
|
||||||
|
.createContextCustomizer(NoMapping.class, null);
|
||||||
|
ConfigurableApplicationContext context = mock(
|
||||||
|
ConfigurableApplicationContext.class);
|
||||||
|
ConfigurableEnvironment environment = mock(ConfigurableEnvironment.class);
|
||||||
|
ConfigurableListableBeanFactory beanFactory = mock(
|
||||||
|
ConfigurableListableBeanFactory.class);
|
||||||
|
given(context.getEnvironment()).willReturn(environment);
|
||||||
|
given(context.getBeanFactory()).willReturn(beanFactory);
|
||||||
|
customizer.customizeContext(context, null);
|
||||||
|
verifyZeroInteractions(environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getContextCustomizerWhenHasTypeMappingShouldReturnCustomizer() {
|
||||||
|
ContextCustomizer customizer = this.factory
|
||||||
|
.createContextCustomizer(TypeMapping.class, null);
|
||||||
|
assertThat(customizer).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getContextCustomizerWhenHasAttributeMappingShouldReturnCustomizer() {
|
||||||
|
ContextCustomizer customizer = this.factory
|
||||||
|
.createContextCustomizer(AttributeMapping.class, null);
|
||||||
|
assertThat(customizer).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hashCodeAndEqualsShoudBeBasedOnPropertyValues() throws Exception {
|
||||||
|
ContextCustomizer customizer1 = this.factory
|
||||||
|
.createContextCustomizer(TypeMapping.class, null);
|
||||||
|
ContextCustomizer customizer2 = this.factory
|
||||||
|
.createContextCustomizer(AttributeMapping.class, null);
|
||||||
|
ContextCustomizer customizer3 = this.factory
|
||||||
|
.createContextCustomizer(OtherMapping.class, null);
|
||||||
|
assertThat(customizer1.hashCode()).isEqualTo(customizer2.hashCode());
|
||||||
|
assertThat(customizer1).isEqualTo(customizer1).isEqualTo(customizer2)
|
||||||
|
.isNotEqualTo(customizer3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void prepareContextShouldAddPropertySource() throws Exception {
|
||||||
|
ContextCustomizer customizer = this.factory
|
||||||
|
.createContextCustomizer(AttributeMapping.class, null);
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
customizer.customizeContext(context, null);
|
||||||
|
assertThat(context.getEnvironment().getProperty("mapped")).isEqualTo("Mapped");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertyMappingShouldNotBeUsedWithComponent() throws Exception {
|
||||||
|
ContextCustomizer customizer = this.factory
|
||||||
|
.createContextCustomizer(AttributeMapping.class, null);
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
context.register(ConfigMapping.class);
|
||||||
|
customizer.customizeContext(context, null);
|
||||||
|
this.thrown.expect(BeanCreationException.class);
|
||||||
|
this.thrown.expectMessage(
|
||||||
|
"@PropertyMapping annotations can only be used on test classes");
|
||||||
|
context.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoMappingAnnotation
|
||||||
|
static class NoMapping {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface NoMappingAnnotation {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeMappingAnnotation
|
||||||
|
static class TypeMapping {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@TypeMappingAnnotation
|
||||||
|
static class ConfigMapping {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@PropertyMapping
|
||||||
|
static @interface TypeMappingAnnotation {
|
||||||
|
|
||||||
|
String mapped() default "Mapped";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AttributeMappingAnnotation
|
||||||
|
static class AttributeMapping {
|
||||||
|
}
|
||||||
|
|
||||||
|
@AttributeMappingAnnotation("Other")
|
||||||
|
static class OtherMapping {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
static @interface AttributeMappingAnnotation {
|
||||||
|
|
||||||
|
@PropertyMapping("mapped")
|
||||||
|
String value() default "Mapped";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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.autoconfigure.properties;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link PropertyMapping @PropertyMapping} annotaions.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@ExampleMapping(exampleProperty = "abc")
|
||||||
|
public class PropertyMappingTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hasProperty() throws Exception {
|
||||||
|
assertThat(this.environment.getProperty("example-property")).isEqualTo("abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue