/*
* 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.test.autoconfigure.properties;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;
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.ClassUtils;
/**
* {@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();
Set<Class<?>> components = new LinkedHashSet<>();
Set<Class<?>> propertyMappings = new LinkedHashSet<>();
while (beanClass != null) {
for (Annotation annotation : AnnotationUtils.getAnnotations(beanClass)) {
if (isAnnotated(annotation, Component.class)) {
components.add(annotation.annotationType());
}
if (isAnnotated(annotation, PropertyMapping.class)) {
propertyMappings.add(annotation.annotationType());
}
}
beanClass = beanClass.getSuperclass();
}
if (!components.isEmpty() && !propertyMappings.isEmpty()) {
throw new IllegalStateException("The @PropertyMapping "
+ getAnnotationsDescription(propertyMappings)
+ " cannot be used in combination with the @Component "
+ getAnnotationsDescription(components));
}
return bean;
}
private boolean isAnnotated(Annotation element,
Class<? extends Annotation> annotationType) {
try {
return element.annotationType().equals(annotationType) || AnnotationUtils
.findAnnotation(element.annotationType(), annotationType) != null;
}
catch (Throwable ex) {
return false;
}
}
private String getAnnotationsDescription(Set<Class<?>> annotations) {
StringBuilder result = new StringBuilder();
for (Class<?> annotation : annotations) {
result.append(result.length() == 0 ? "" : ", ");
result.append("@" + ClassUtils.getShortName(annotation));
}
result.insert(0, annotations.size() == 1 ? "annotation " : "annotations ");
return result.toString();
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}
}