|
|
|
|
@@ -16,11 +16,20 @@
|
|
|
|
|
|
|
|
|
|
package org.springframework.boot.autoconfigure.condition;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Collection;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
import java.util.LinkedList;
|
|
|
|
|
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;
|
|
|
|
|
import org.springframework.context.annotation.Condition;
|
|
|
|
|
import org.springframework.context.annotation.ConditionContext;
|
|
|
|
|
@@ -31,24 +40,113 @@ import org.springframework.util.ClassUtils;
|
|
|
|
|
import org.springframework.util.MultiValueMap;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* {@link Condition} that checks for the presence or absence of specific classes.
|
|
|
|
|
* {@link Condition} and {@link AutoConfigurationImportFilter} that checks for the
|
|
|
|
|
* presence or absence of specific classes.
|
|
|
|
|
*
|
|
|
|
|
* @author Phillip Webb
|
|
|
|
|
* @see ConditionalOnClass
|
|
|
|
|
* @see ConditionalOnMissingClass
|
|
|
|
|
*/
|
|
|
|
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
|
|
|
|
class OnClassCondition extends SpringBootCondition {
|
|
|
|
|
class OnClassCondition extends SpringBootCondition
|
|
|
|
|
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
|
|
|
|
|
|
|
|
|
|
private BeanFactory beanFactory;
|
|
|
|
|
|
|
|
|
|
private ClassLoader beanClassLoader;
|
|
|
|
|
|
|
|
|
|
@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,
|
|
|
|
|
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
|
|
|
|
|
// things worse
|
|
|
|
|
int split = autoConfigurationClasses.length / 2;
|
|
|
|
|
GetOutcomesThread thread = new GetOutcomesThread(autoConfigurationClasses, 0,
|
|
|
|
|
split, autoConfigurationMetadata);
|
|
|
|
|
thread.start();
|
|
|
|
|
ConditionOutcome[] secondHalf = getOutcomes(autoConfigurationClasses, split,
|
|
|
|
|
autoConfigurationClasses.length, autoConfigurationMetadata);
|
|
|
|
|
try {
|
|
|
|
|
thread.join();
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException ex) {
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
}
|
|
|
|
|
ConditionOutcome[] firstHalf = thread.getResult();
|
|
|
|
|
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
|
|
|
|
|
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
|
|
|
|
|
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
|
|
|
|
|
return outcomes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses,
|
|
|
|
|
int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return outcomes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ConditionOutcome getOutcome(Set<String> candidates) {
|
|
|
|
|
try {
|
|
|
|
|
List<String> missing = getMatches(candidates, MatchType.MISSING,
|
|
|
|
|
this.beanClassLoader);
|
|
|
|
|
if (!missing.isEmpty()) {
|
|
|
|
|
return ConditionOutcome
|
|
|
|
|
.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
|
|
|
|
|
.didNotFind("required class", "required classes")
|
|
|
|
|
.items(Style.QUOTE, missing));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) {
|
|
|
|
|
// We'll get another chance later
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
|
|
|
|
AnnotatedTypeMetadata metadata) {
|
|
|
|
|
ClassLoader classLoader = context.getClassLoader();
|
|
|
|
|
ConditionMessage matchMessage = ConditionMessage.empty();
|
|
|
|
|
MultiValueMap<String, Object> onClasses = getAttributes(metadata,
|
|
|
|
|
ConditionalOnClass.class);
|
|
|
|
|
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
|
|
|
|
|
if (onClasses != null) {
|
|
|
|
|
List<String> missing = getMatchingClasses(onClasses, MatchType.MISSING,
|
|
|
|
|
context);
|
|
|
|
|
List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
|
|
|
|
|
if (!missing.isEmpty()) {
|
|
|
|
|
return ConditionOutcome
|
|
|
|
|
.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
|
|
|
|
|
@@ -57,13 +155,13 @@ class OnClassCondition extends SpringBootCondition {
|
|
|
|
|
}
|
|
|
|
|
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
|
|
|
|
|
.found("required class", "required classes").items(Style.QUOTE,
|
|
|
|
|
getMatchingClasses(onClasses, MatchType.PRESENT, context));
|
|
|
|
|
getMatches(onClasses, MatchType.PRESENT, classLoader));
|
|
|
|
|
}
|
|
|
|
|
MultiValueMap<String, Object> onMissingClasses = getAttributes(metadata,
|
|
|
|
|
List<String> onMissingClasses = getCandidates(metadata,
|
|
|
|
|
ConditionalOnMissingClass.class);
|
|
|
|
|
if (onMissingClasses != null) {
|
|
|
|
|
List<String> present = getMatchingClasses(onMissingClasses, MatchType.PRESENT,
|
|
|
|
|
context);
|
|
|
|
|
List<String> present = getMatches(onMissingClasses, MatchType.PRESENT,
|
|
|
|
|
classLoader);
|
|
|
|
|
if (!present.isEmpty()) {
|
|
|
|
|
return ConditionOutcome.noMatch(
|
|
|
|
|
ConditionMessage.forCondition(ConditionalOnMissingClass.class)
|
|
|
|
|
@@ -71,30 +169,23 @@ class OnClassCondition extends SpringBootCondition {
|
|
|
|
|
.items(Style.QUOTE, present));
|
|
|
|
|
}
|
|
|
|
|
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
|
|
|
|
|
.didNotFind("unwanted class", "unwanted classes")
|
|
|
|
|
.items(Style.QUOTE, getMatchingClasses(onMissingClasses,
|
|
|
|
|
MatchType.MISSING, context));
|
|
|
|
|
.didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE,
|
|
|
|
|
getMatches(onMissingClasses, MatchType.MISSING, classLoader));
|
|
|
|
|
}
|
|
|
|
|
return ConditionOutcome.match(matchMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private MultiValueMap<String, Object> getAttributes(AnnotatedTypeMetadata metadata,
|
|
|
|
|
private List<String> getCandidates(AnnotatedTypeMetadata metadata,
|
|
|
|
|
Class<?> annotationType) {
|
|
|
|
|
return metadata.getAllAnnotationAttributes(annotationType.getName(), true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<String> getMatchingClasses(MultiValueMap<String, Object> attributes,
|
|
|
|
|
MatchType matchType, ConditionContext context) {
|
|
|
|
|
List<String> matches = new LinkedList<String>();
|
|
|
|
|
addAll(matches, attributes.get("value"));
|
|
|
|
|
addAll(matches, attributes.get("name"));
|
|
|
|
|
Iterator<String> iterator = matches.iterator();
|
|
|
|
|
while (iterator.hasNext()) {
|
|
|
|
|
if (!matchType.matches(iterator.next(), context)) {
|
|
|
|
|
iterator.remove();
|
|
|
|
|
}
|
|
|
|
|
MultiValueMap<String, Object> attributes = metadata
|
|
|
|
|
.getAllAnnotationAttributes(annotationType.getName(), true);
|
|
|
|
|
List<String> candidates = new ArrayList<String>();
|
|
|
|
|
if (attributes == null) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
return matches;
|
|
|
|
|
addAll(candidates, attributes.get("value"));
|
|
|
|
|
addAll(candidates, attributes.get("name"));
|
|
|
|
|
return candidates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void addAll(List<String> list, List<Object> itemsToAdd) {
|
|
|
|
|
@@ -105,13 +196,34 @@ class OnClassCondition extends SpringBootCondition {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<String> getMatches(Collection<String> candiates, MatchType matchType,
|
|
|
|
|
ClassLoader classLoader) {
|
|
|
|
|
List<String> matches = new ArrayList<String>(candiates.size());
|
|
|
|
|
for (String candidate : candiates) {
|
|
|
|
|
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, ConditionContext context) {
|
|
|
|
|
return isPresent(className, context.getClassLoader());
|
|
|
|
|
public boolean matches(String className, ClassLoader classLoader) {
|
|
|
|
|
return isPresent(className, classLoader);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
@@ -119,8 +231,8 @@ class OnClassCondition extends SpringBootCondition {
|
|
|
|
|
MISSING {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean matches(String className, ConditionContext context) {
|
|
|
|
|
return !isPresent(className, context.getClassLoader());
|
|
|
|
|
public boolean matches(String className, ClassLoader classLoader) {
|
|
|
|
|
return !isPresent(className, classLoader);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
@@ -146,8 +258,39 @@ class OnClassCondition extends SpringBootCondition {
|
|
|
|
|
return Class.forName(className);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract boolean matches(String className, ConditionContext context);
|
|
|
|
|
public abstract boolean matches(String className, ClassLoader classLoader);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class GetOutcomesThread extends Thread {
|
|
|
|
|
|
|
|
|
|
private final String[] autoConfigurationClasses;
|
|
|
|
|
|
|
|
|
|
private final int start;
|
|
|
|
|
|
|
|
|
|
private final int end;
|
|
|
|
|
|
|
|
|
|
private final AutoConfigurationMetadata autoConfigurationMetadata;
|
|
|
|
|
|
|
|
|
|
private ConditionOutcome[] outcomes;
|
|
|
|
|
|
|
|
|
|
GetOutcomesThread(String[] autoConfigurationClasses, int start, int end,
|
|
|
|
|
AutoConfigurationMetadata autoConfigurationMetadata) {
|
|
|
|
|
this.autoConfigurationClasses = autoConfigurationClasses;
|
|
|
|
|
this.start = start;
|
|
|
|
|
this.end = end;
|
|
|
|
|
this.autoConfigurationMetadata = autoConfigurationMetadata;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
this.outcomes = getOutcomes(this.autoConfigurationClasses, this.start,
|
|
|
|
|
this.end, this.autoConfigurationMetadata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ConditionOutcome[] getResult() {
|
|
|
|
|
return this.outcomes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|