Commit 4d705dff authored by Phillip Webb's avatar Phillip Webb

Merge branch 'gh-13328'

parents 5765ed00 ff98ba0f
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
......@@ -45,7 +45,8 @@ public interface AutoConfigurationImportFilter {
/**
* Apply the filter to the given auto-configuration class candidates.
* @param autoConfigurationClasses the auto-configuration classes being considered.
* Implementations should not change the values in this array.
* This array may contain {@code null} elements. Implementations should not change the
* values in this array.
* @param autoConfigurationMetadata access to the meta-data generated by the
* auto-configure annotation processor
* @return a boolean array indicating which of the auto-configuration classes should
......
......@@ -263,6 +263,7 @@ public class AutoConfigurationImportSelector
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
candidates[i] = null;
skipped = true;
}
}
......
......@@ -29,6 +29,7 @@ import java.util.SortedMap;
import java.util.TreeMap;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
......@@ -159,6 +160,20 @@ public final class ConditionEvaluationReport {
return this.parent;
}
/**
* Attempt to find the {@link ConditionEvaluationReport} for the specified bean
* factory.
* @param beanFactory the bean factory (may be {@code null})
* @return the {@link ConditionEvaluationReport} or {@code null}
*/
public static ConditionEvaluationReport find(BeanFactory beanFactory) {
if (beanFactory != null && beanFactory instanceof ConfigurableBeanFactory) {
return ConditionEvaluationReport
.get((ConfigurableListableBeanFactory) beanFactory);
}
return null;
}
/**
* Obtain a {@link ConditionEvaluationReport} for the specified bean factory.
* @param beanFactory the bean factory
......
/*
* Copyright 2012-2018 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.condition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
* Abstract base class for a {@link SpringBootCondition} that also implements
* {@link AutoConfigurationImportFilter}.
*
* @author Phillip Webb
*/
abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
private BeanFactory beanFactory;
private ClassLoader beanClassLoader;
@Override
public boolean[] match(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport
.find(this.beanFactory);
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this,
outcomes[i]);
}
}
}
return match;
}
protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata);
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
protected final BeanFactory getBeanFactory() {
return this.beanFactory;
}
protected final ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
protected List<String> filter(Collection<String> classNames,
ClassNameFilter classNameFilter, ClassLoader classLoader) {
if (CollectionUtils.isEmpty(classNames)) {
return Collections.emptyList();
}
List<String> matches = new ArrayList<>(classNames.size());
for (String candidate : classNames) {
if (classNameFilter.matches(candidate, classLoader)) {
matches.add(candidate);
}
}
return matches;
}
protected enum ClassNameFilter {
PRESENT {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return isPresent(className, classLoader);
}
},
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
public abstract boolean matches(String className, ClassLoader classLoader);
public static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
forName(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;
}
}
private static Class<?> forName(String className, ClassLoader classLoader)
throws ClassNotFoundException {
if (classLoader != null) {
return classLoader.loadClass(className);
}
return Class.forName(className);
}
}
}
......@@ -34,6 +34,7 @@ import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
......@@ -58,7 +59,8 @@ import org.springframework.util.StringUtils;
* @author Andy Wilkinson
*/
@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
class OnBeanCondition extends FilteringSpringBootCondition
implements ConfigurationCondition {
/**
* Bean definition attribute name for factory beans to signal their product type (if
......@@ -66,6 +68,40 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
*/
public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE;
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
Set<String> onBeanTypes = autoConfigurationMetadata
.getSet(autoConfigurationClass, "ConditionalOnBean");
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
if (outcomes[i] == null) {
Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(
autoConfigurationClass, "ConditionalOnSingleCandidate");
outcomes[i] = getOutcome(onSingleCandidateTypes,
ConditionalOnSingleCandidate.class);
}
}
}
return outcomes;
}
private ConditionOutcome getOutcome(Set<String> requiredBeanTypes,
Class<? extends Annotation> annotation) {
List<String> missing = filter(requiredBeanTypes, ClassNameFilter.MISSING,
getBeanClassLoader());
if (!missing.isEmpty()) {
ConditionMessage message = ConditionMessage.forCondition(annotation)
.didNotFind("required type", "required types")
.items(Style.QUOTE, missing);
return ConditionOutcome.noMatch(message);
}
return null;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
......@@ -337,7 +373,6 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
.getParentBeanFactory()), beanName, considerHierarchy);
}
return null;
}
private static class BeanSearchSpec {
......
......@@ -18,17 +18,10 @@ package org.springframework.boot.autoconfigure.condition;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
......@@ -37,7 +30,6 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
/**
......@@ -49,43 +41,10 @@ import org.springframework.util.MultiValueMap;
* @see ConditionalOnMissingClass
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
private BeanFactory beanFactory;
private ClassLoader beanClassLoader;
class OnClassCondition extends FilteringSpringBootCondition {
@Override
public boolean[] match(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = getConditionEvaluationReport();
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this,
outcomes[i]);
}
}
}
return match;
}
private ConditionEvaluationReport getConditionEvaluationReport() {
if (this.beanFactory != null
&& this.beanFactory instanceof ConfigurableBeanFactory) {
return ConditionEvaluationReport
.get((ConfigurableListableBeanFactory) this.beanFactory);
}
return null;
}
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// Split the work and perform half in a background thread. Using a single
// additional thread seems to offer the best performance. More threads make
......@@ -95,7 +54,7 @@ class OnClassCondition extends SpringBootCondition
autoConfigurationClasses, 0, split, autoConfigurationMetadata);
OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
autoConfigurationClasses, split, autoConfigurationClasses.length,
autoConfigurationMetadata, this.beanClassLoader);
autoConfigurationMetadata, getBeanClassLoader());
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
......@@ -108,7 +67,7 @@ class OnClassCondition extends SpringBootCondition
int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
OutcomesResolver outcomesResolver = new StandardOutcomesResolver(
autoConfigurationClasses, start, end, autoConfigurationMetadata,
this.beanClassLoader);
getBeanClassLoader());
try {
return new ThreadedOutcomesResolver(outcomesResolver);
}
......@@ -124,7 +83,8 @@ class OnClassCondition extends SpringBootCondition
ConditionMessage matchMessage = ConditionMessage.empty();
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
List<String> missing = filter(onClasses, ClassNameFilter.MISSING,
classLoader);
if (!missing.isEmpty()) {
return ConditionOutcome
.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
......@@ -133,12 +93,12 @@ class OnClassCondition extends SpringBootCondition
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes").items(Style.QUOTE,
getMatches(onClasses, MatchType.PRESENT, classLoader));
filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
List<String> onMissingClasses = getCandidates(metadata,
ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
List<String> present = getMatches(onMissingClasses, MatchType.PRESENT,
List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT,
classLoader);
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(
......@@ -147,8 +107,9 @@ class OnClassCondition extends SpringBootCondition
.items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE,
getMatches(onMissingClasses, MatchType.MISSING, classLoader));
.didNotFind("unwanted class", "unwanted classes")
.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING,
classLoader));
}
return ConditionOutcome.match(matchMessage);
}
......@@ -174,72 +135,6 @@ class OnClassCondition extends SpringBootCondition
}
}
private List<String> getMatches(Collection<String> candidates, MatchType matchType,
ClassLoader classLoader) {
List<String> matches = new ArrayList<>(candidates.size());
for (String candidate : candidates) {
if (matchType.matches(candidate, classLoader)) {
matches.add(candidate);
}
}
return matches;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
private enum MatchType {
PRESENT {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return isPresent(className, classLoader);
}
},
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
private static boolean isPresent(String className, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassUtils.getDefaultClassLoader();
}
try {
forName(className, classLoader);
return true;
}
catch (Throwable ex) {
return false;
}
}
private static Class<?> forName(String className, ClassLoader classLoader)
throws ClassNotFoundException {
if (classLoader != null) {
return classLoader.loadClass(className);
}
return Class.forName(className);
}
public abstract boolean matches(String className, ClassLoader classLoader);
}
private interface OutcomesResolver {
ConditionOutcome[] resolveOutcomes();
......@@ -304,10 +199,12 @@ class OnClassCondition extends SpringBootCondition
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
for (int i = start; i < end; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
Set<String> candidates = autoConfigurationMetadata
.getSet(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
outcomes[i - start] = getOutcome(candidates);
if (autoConfigurationClass != null) {
Set<String> candidates = autoConfigurationMetadata
.getSet(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
outcomes[i - start] = getOutcome(candidates);
}
}
}
return outcomes;
......@@ -315,7 +212,7 @@ class OnClassCondition extends SpringBootCondition
private ConditionOutcome getOutcome(Set<String> candidates) {
try {
List<String> missing = getMatches(candidates, MatchType.MISSING,
List<String> missing = filter(candidates, ClassNameFilter.MISSING,
this.beanClassLoader);
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(
......
......@@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebEnvironment;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
......@@ -36,14 +37,60 @@ import org.springframework.web.context.WebApplicationContext;
* {@link WebApplicationContext}.
*
* @author Dave Syer
* @author Phillip Webb
* @see ConditionalOnWebApplication
* @see ConditionalOnNotWebApplication
*/
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends SpringBootCondition {
class OnWebApplicationCondition extends FilteringSpringBootCondition {
private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context."
+ "support.GenericWebApplicationContext";
private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";
private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";
@Override
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
outcomes[i] = getOutcome(autoConfigurationMetadata
.get(autoConfigurationClass, "ConditionalOnWebApplication"));
}
}
return outcomes;
}
private ConditionOutcome getOutcome(String type) {
if (type == null) {
return null;
}
ConditionMessage.Builder message = ConditionMessage
.forCondition(ConditionalOnWebApplication.class);
if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS,
getBeanClassLoader())) {
return ConditionOutcome.noMatch(
message.didNotFind("servlet web application classes").atAll());
}
}
if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {
if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS,
getBeanClassLoader())) {
return ConditionOutcome.noMatch(
message.didNotFind("reactive web application classes").atAll());
}
}
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS,
getBeanClassLoader())
&& !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS,
getBeanClassLoader())) {
return ConditionOutcome.noMatch(message
.didNotFind("reactive or servlet web application classes").atAll());
}
return null;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
......@@ -93,9 +140,10 @@ class OnWebApplicationCondition extends SpringBootCondition {
private ConditionOutcome isServletWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) {
return ConditionOutcome
.noMatch(message.didNotFind("web application classes").atAll());
if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS,
context.getClassLoader())) {
return ConditionOutcome.noMatch(
message.didNotFind("servlet web application classes").atAll());
}
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
......@@ -115,6 +163,11 @@ class OnWebApplicationCondition extends SpringBootCondition {
private ConditionOutcome isReactiveWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS,
context.getClassLoader())) {
return ConditionOutcome.noMatch(
message.didNotFind("reactive web application classes").atAll());
}
if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) {
return ConditionOutcome
.match(message.foundExactly("ConfigurableReactiveWebEnvironment"));
......
......@@ -17,7 +17,9 @@ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoCo
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
......
......@@ -18,14 +18,15 @@ package org.springframework.boot.autoconfigureprocessor;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
......@@ -36,10 +37,8 @@ import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
......@@ -52,6 +51,9 @@ import javax.tools.StandardLocation;
*/
@SupportedAnnotationTypes({ "org.springframework.context.annotation.Configuration",
"org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
"org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
"org.springframework.boot.autoconfigure.AutoConfigureBefore",
"org.springframework.boot.autoconfigure.AutoConfigureAfter",
"org.springframework.boot.autoconfigure.AutoConfigureOrder" })
......@@ -60,7 +62,9 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
protected static final String PROPERTIES_PATH = "META-INF/"
+ "spring-autoconfigure-metadata.properties";
private Map<String, String> annotations;
private final Map<String, String> annotations;
private final Map<String, ValueExtractor> valueExtractors;
private final Properties properties = new Properties();
......@@ -68,6 +72,9 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
Map<String, String> annotations = new LinkedHashMap<>();
addAnnotations(annotations);
this.annotations = Collections.unmodifiableMap(annotations);
Map<String, ValueExtractor> valueExtractors = new LinkedHashMap<>();
addValueExtractors(valueExtractors);
this.valueExtractors = Collections.unmodifiableMap(valueExtractors);
}
protected void addAnnotations(Map<String, String> annotations) {
......@@ -75,6 +82,12 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
"org.springframework.context.annotation.Configuration");
annotations.put("ConditionalOnClass",
"org.springframework.boot.autoconfigure.condition.ConditionalOnClass");
annotations.put("ConditionalOnBean",
"org.springframework.boot.autoconfigure.condition.ConditionalOnBean");
annotations.put("ConditionalOnSingleCandidate",
"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate");
annotations.put("ConditionalOnWebApplication",
"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication");
annotations.put("AutoConfigureBefore",
"org.springframework.boot.autoconfigure.AutoConfigureBefore");
annotations.put("AutoConfigureAfter",
......@@ -83,6 +96,18 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
"org.springframework.boot.autoconfigure.AutoConfigureOrder");
}
private void addValueExtractors(Map<String, ValueExtractor> attributes) {
attributes.put("Configuration", ValueExtractor.allFrom("value"));
attributes.put("ConditionalOnClass", ValueExtractor.allFrom("value", "name"));
attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
attributes.put("ConditionalOnSingleCandidate",
new OnBeanConditionValueExtractor());
attributes.put("ConditionalOnWebApplication", ValueExtractor.allFrom("type"));
attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name"));
attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name"));
attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value"));
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
......@@ -123,10 +148,10 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
private void processElement(Element element, String propertyKey,
String annotationName) {
try {
String qualifiedName = getQualifiedName(element);
String qualifiedName = Elements.getQualifiedName(element);
AnnotationMirror annotation = getAnnotation(element, annotationName);
if (qualifiedName != null && annotation != null) {
List<Object> values = getValues(annotation);
List<Object> values = getValues(propertyKey, annotation);
this.properties.put(qualifiedName + "." + propertyKey,
toCommaDelimitedString(values));
this.properties.put(qualifiedName, "");
......@@ -158,68 +183,89 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
return result.toString();
}
private List<Object> getValues(AnnotationMirror annotation) {
return annotation.getElementValues().entrySet().stream()
.filter(this::isNameOrValueAttribute).flatMap(this::getValues)
.collect(Collectors.toList());
}
private boolean isNameOrValueAttribute(Entry<? extends ExecutableElement, ?> entry) {
String attributeName = entry.getKey().getSimpleName().toString();
return "name".equals(attributeName) || "value".equals(attributeName);
private List<Object> getValues(String propertyKey, AnnotationMirror annotation) {
ValueExtractor extractor = this.valueExtractors.get(propertyKey);
if (extractor == null) {
return Collections.emptyList();
}
return extractor.getValues(annotation);
}
@SuppressWarnings("unchecked")
private Stream<Object> getValues(Entry<?, ? extends AnnotationValue> entry) {
Object value = entry.getValue().getValue();
if (value instanceof List) {
return ((List<AnnotationValue>) value).stream()
.map((annotation) -> processValue(annotation.getValue()));
private void writeProperties() throws IOException {
if (!this.properties.isEmpty()) {
FileObject file = this.processingEnv.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
try (OutputStream outputStream = file.openOutputStream()) {
this.properties.store(outputStream, null);
}
}
return Stream.of(processValue(value));
}
private Object processValue(Object value) {
if (value instanceof DeclaredType) {
return getQualifiedName(((DeclaredType) value).asElement());
@FunctionalInterface
private interface ValueExtractor {
List<Object> getValues(AnnotationMirror annotation);
static ValueExtractor allFrom(String... attributes) {
Set<String> names = new HashSet<>(Arrays.asList(attributes));
return new AbstractValueExtractor() {
@Override
public List<Object> getValues(AnnotationMirror annotation) {
List<Object> result = new ArrayList<>();
annotation.getElementValues().forEach((key, value) -> {
if (names.contains(key.getSimpleName().toString())) {
extractValues(value).forEach(result::add);
}
});
return result;
}
};
}
return value;
}
private String getQualifiedName(Element element) {
if (element != null) {
TypeElement enclosingElement = getEnclosingTypeElement(element.asType());
if (enclosingElement != null) {
return getQualifiedName(enclosingElement) + "$"
+ ((DeclaredType) element.asType()).asElement().getSimpleName()
.toString();
private abstract static class AbstractValueExtractor implements ValueExtractor {
@SuppressWarnings("unchecked")
protected Stream<Object> extractValues(AnnotationValue annotationValue) {
if (annotationValue == null) {
return Stream.empty();
}
if (element instanceof TypeElement) {
return ((TypeElement) element).getQualifiedName().toString();
Object value = annotationValue.getValue();
if (value instanceof List) {
return ((List<AnnotationValue>) value).stream()
.map((annotation) -> extractValue(annotation.getValue()));
}
return Stream.of(extractValue(value));
}
return null;
}
private TypeElement getEnclosingTypeElement(TypeMirror type) {
if (type instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) type;
Element enclosingElement = declaredType.asElement().getEnclosingElement();
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
return (TypeElement) enclosingElement;
private Object extractValue(Object value) {
if (value instanceof DeclaredType) {
return Elements.getQualifiedName(((DeclaredType) value).asElement());
}
return value;
}
return null;
}
private void writeProperties() throws IOException {
if (!this.properties.isEmpty()) {
FileObject file = this.processingEnv.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
try (OutputStream outputStream = file.openOutputStream()) {
this.properties.store(outputStream, null);
private static class OnBeanConditionValueExtractor extends AbstractValueExtractor {
@Override
public List<Object> getValues(AnnotationMirror annotation) {
Map<String, AnnotationValue> attributes = new LinkedHashMap<>();
annotation.getElementValues().forEach((key, value) -> attributes
.put(key.getSimpleName().toString(), value));
if (attributes.containsKey("name")) {
return Collections.emptyList();
}
List<Object> result = new ArrayList<>();
extractValues(attributes.get("value")).forEach(result::add);
extractValues(attributes.get("type")).forEach(result::add);
return result;
}
}
}
/*
* Copyright 2012-2018 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.autoconfigureprocessor;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
/**
* Utilities for dealing with {@link Element} classes.
*
* @author Phillip Webb
*/
final class Elements {
private Elements() {
}
static String getQualifiedName(Element element) {
if (element != null) {
TypeElement enclosingElement = getEnclosingTypeElement(element.asType());
if (enclosingElement != null) {
return getQualifiedName(enclosingElement) + "$"
+ ((DeclaredType) element.asType()).asElement().getSimpleName()
.toString();
}
if (element instanceof TypeElement) {
return ((TypeElement) element).getQualifiedName().toString();
}
}
return null;
}
private static TypeElement getEnclosingTypeElement(TypeMirror type) {
if (type instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) type;
Element enclosingElement = declaredType.asElement().getEnclosingElement();
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
return (TypeElement) enclosingElement;
}
}
return null;
}
}
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
......@@ -50,18 +50,42 @@ public class AutoConfigureAnnotationProcessorTests {
@Test
public void annotatedClass() throws Exception {
Properties properties = compile(TestClassConfiguration.class);
assertThat(properties).hasSize(3);
assertThat(properties).hasSize(6);
assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration.ConditionalOnClass",
"java.io.InputStream,org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration$Nested");
assertThat(properties).containsKey(
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration");
assertThat(properties).containsKey(
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.Configuration");
assertThat(properties).doesNotContainKey(
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested");
assertThat(properties)
.containsKey("org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration");
assertThat(properties)
.containsKey("org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration.Configuration");
assertThat(properties)
.doesNotContainKey("org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration$Nested");
assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration.ConditionalOnBean",
"java.io.OutputStream");
assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration.ConditionalOnSingleCandidate",
"java.io.OutputStream");
assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration.ConditionalOnWebApplication",
"SERVLET");
}
@Test
public void annoatedClassWithOnBeanThatHasName() throws Exception {
Properties properties = compile(TestOnBeanWithNameClassConfiguration.class);
assertThat(properties).hasSize(3);
assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestOnBeanWithNameClassConfiguration.ConditionalOnBean",
"");
}
@Test
......@@ -104,7 +128,7 @@ public class AutoConfigureAnnotationProcessorTests {
}
private Properties compile(Class<?>... types) throws IOException {
TestConditionMetadataAnnotationProcessor processor = new TestConditionMetadataAnnotationProcessor(
TestAutoConfigureAnnotationProcessor processor = new TestAutoConfigureAnnotationProcessor(
this.compiler.getOutputLocation());
this.compiler.getTask(types).call(processor);
return processor.getWrittenProperties();
......
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
......@@ -32,15 +32,18 @@ import javax.annotation.processing.SupportedAnnotationTypes;
@SupportedAnnotationTypes({
"org.springframework.boot.autoconfigureprocessor.TestConfiguration",
"org.springframework.boot.autoconfigureprocessor.TestConditionalOnClass",
"org.springframework.boot.autoconfigure.condition.TestConditionalOnBean",
"org.springframework.boot.autoconfigure.condition.TestConditionalOnSingleCandidate",
"org.springframework.boot.autoconfigure.condition.TestConditionalOnWebApplication",
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigureBefore",
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigureAfter",
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigureOrder" })
public class TestConditionMetadataAnnotationProcessor
public class TestAutoConfigureAnnotationProcessor
extends AutoConfigureAnnotationProcessor {
private final File outputLocation;
public TestConditionMetadataAnnotationProcessor(File outputLocation) {
public TestAutoConfigureAnnotationProcessor(File outputLocation) {
this.outputLocation = outputLocation;
}
......@@ -48,6 +51,11 @@ public class TestConditionMetadataAnnotationProcessor
protected void addAnnotations(Map<String, String> annotations) {
put(annotations, "Configuration", TestConfiguration.class);
put(annotations, "ConditionalOnClass", TestConditionalOnClass.class);
put(annotations, "ConditionalOnBean", TestConditionalOnBean.class);
put(annotations, "ConditionalOnSingleCandidate",
TestConditionalOnSingleCandidate.class);
put(annotations, "ConditionalOnWebApplication",
TestConditionalOnWebApplication.class);
put(annotations, "AutoConfigureBefore", TestAutoConfigureBefore.class);
put(annotations, "AutoConfigureAfter", TestAutoConfigureAfter.class);
put(annotations, "AutoConfigureOrder", TestAutoConfigureOrder.class);
......
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
......@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigureprocessor;
import org.springframework.boot.autoconfigureprocessor.TestConditionalOnWebApplication.Type;
/**
* Test configuration with an annotated class.
*
......@@ -23,6 +25,9 @@ package org.springframework.boot.autoconfigureprocessor;
*/
@TestConfiguration
@TestConditionalOnClass(name = "java.io.InputStream", value = TestClassConfiguration.Nested.class)
@TestConditionalOnBean(type = "java.io.OutputStream")
@TestConditionalOnSingleCandidate(type = "java.io.OutputStream")
@TestConditionalOnWebApplication(type = Type.SERVLET)
public class TestClassConfiguration {
@TestAutoConfigureOrder
......
/*
* Copyright 2012-2018 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.autoconfigureprocessor;
import java.lang.annotation.Annotation;
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;
/**
* Alternative to Spring Boot's {@code ConditionalOnBean} for testing (removes the need
* for a dependency on the real annotation).
*
* @author Phillip Webb
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestConditionalOnBean {
Class<?>[] value() default {};
String[] type() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
}
/*
* Copyright 2012-2018 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.autoconfigureprocessor;
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;
/**
* Alternative to Spring Boot's {@code ConditionalOnSingleCandidate} for testing (removes
* the need for a dependency on the real annotation).
*
* @author Phillip Webb
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestConditionalOnSingleCandidate {
Class<?> value() default Object.class;
String type() default "";
}
/*
* Copyright 2012-2018 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.autoconfigureprocessor;
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;
/**
* Alternative to Spring Boot's {@code @ConditionalOnWebApplication} for testing (removes
* the need for a dependency on the real annotation).
*
* @author Phillip Webb
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestConditionalOnWebApplication {
Type type() default Type.ANY;
enum Type {
ANY, SERVLET, REACTIVE
}
}
/*
* Copyright 2012-2018 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.autoconfigureprocessor;
/**
* Test configuration with an annotated class.
*
* @author Phillip Webb
*/
@TestConfiguration
@TestConditionalOnBean(name = "test", type = "java.io.OutputStream")
public class TestOnBeanWithNameClassConfiguration {
}
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