@ -16,11 +16,20 @@
@@ -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;
@@ -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 {
@@ -57,13 +155,13 @@ class OnClassCondition extends SpringBootCondition {
}
matchMessage = matchMessage . andCondition ( ConditionalOnClass . class )
. found ( "required class" , "required classes" ) . items ( Style . QUOTE ,
getMatchingClass es ( onClasses , MatchType . PRESENT , context ) ) ;
getMatches ( onClasses , MatchType . PRESENT , classLoader ) ) ;
}
MultiValueMap < String , Object > onMissingClasses = getAttribu tes ( metadata ,
List < String > onMissingClasses = getCandida tes ( metadata ,
ConditionalOnMissingClass . class ) ;
if ( onMissingClasses ! = null ) {
List < String > present = getMatchingClass es ( 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 {
@@ -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 > getAttribu tes( AnnotatedTypeMetadata metadata ,
private List < String > getCandida tes( 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 {
@@ -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 {
@@ -119,8 +231,8 @@ class OnClassCondition extends SpringBootCondition {
MISSING {
@Override
public boolean matches ( String className , ConditionContext context ) {
return ! isPresent ( className , context . getC lassLoader ( ) ) ;
public boolean matches ( String className , ClassLoader classLoader ) {
return ! isPresent ( className , classLoader ) ;
}
} ;
@ -146,8 +258,39 @@ class OnClassCondition extends SpringBootCondition {
@@ -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 ;
}
}
}