Add AllNestedConditions and NoneOfNestedConditions

Fixes gh-2400
pull/3532/merge
Eric Fenderbosch 10 years ago committed by Dave Syer
parent 721b5a2395
commit ea4061fe99

@ -0,0 +1,134 @@
package org.springframework.boot.autoconfigure.condition;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
public abstract class AbstractNestedCondition extends SpringBootCondition implements
ConfigurationCondition {
private final ConfigurationPhase configurationPhase;
public AbstractNestedCondition(ConfigurationPhase configurationPhase) {
Assert.notNull(configurationPhase, "ConfigurationPhase must not be null");
this.configurationPhase = configurationPhase;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return this.configurationPhase;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
MemberConditions memberConditions = new MemberConditions(context, getClass()
.getName());
List<ConditionOutcome> outcomes = memberConditions.getMatchOutcomes();
return buildConditionOutcome(outcomes);
}
protected abstract ConditionOutcome buildConditionOutcome(
List<ConditionOutcome> outcomes);
private static class MemberConditions {
private final ConditionContext context;
private final MetadataReaderFactory readerFactory;
private final Map<AnnotationMetadata, List<Condition>> memberConditions;
public MemberConditions(ConditionContext context, String className) {
this.context = context;
this.readerFactory = new SimpleMetadataReaderFactory(
context.getResourceLoader());
String[] members = getMetadata(className).getMemberClassNames();
this.memberConditions = getMemberConditions(members);
}
private Map<AnnotationMetadata, List<Condition>> getMemberConditions(
String[] members) {
MultiValueMap<AnnotationMetadata, Condition> memberConditions = new LinkedMultiValueMap<AnnotationMetadata, Condition>();
for (String member : members) {
AnnotationMetadata metadata = getMetadata(member);
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass);
memberConditions.add(metadata, condition);
}
}
}
return Collections.unmodifiableMap(memberConditions);
}
private AnnotationMetadata getMetadata(String className) {
try {
return this.readerFactory.getMetadataReader(className)
.getAnnotationMetadata();
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
@SuppressWarnings("unchecked")
private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(Conditional.class.getName(), true);
Object values = (attributes != null ? attributes.get("value") : null);
return (List<String[]>) (values != null ? values : Collections.emptyList());
}
private Condition getCondition(String conditionClassName) {
Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName,
this.context.getClassLoader());
return (Condition) BeanUtils.instantiateClass(conditionClass);
}
public List<ConditionOutcome> getMatchOutcomes() {
List<ConditionOutcome> outcomes = new ArrayList<ConditionOutcome>();
for (Map.Entry<AnnotationMetadata, List<Condition>> entry : this.memberConditions
.entrySet()) {
AnnotationMetadata metadata = entry.getKey();
for (Condition condition : entry.getValue()) {
outcomes.add(getConditionOutcome(metadata, condition));
}
}
return Collections.unmodifiableList(outcomes);
}
private ConditionOutcome getConditionOutcome(AnnotationMetadata metadata,
Condition condition) {
String messagePrefix = "member condition on " + metadata.getClassName();
if (condition instanceof SpringBootCondition) {
ConditionOutcome outcome = ((SpringBootCondition) condition)
.getMatchOutcome(this.context, metadata);
String message = outcome.getMessage();
return new ConditionOutcome(outcome.isMatch(), messagePrefix
+ (StringUtils.hasLength(message) ? " : " + message : ""));
}
boolean matches = condition.matches(this.context, metadata);
return new ConditionOutcome(matches, messagePrefix);
}
}
}

@ -0,0 +1,52 @@
package org.springframework.boot.autoconfigure.condition;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Condition;
/**
* {@link Condition} that will match when all nested class conditions match.
* Can be used to create composite conditions, for example:
*
* <pre class="code">
* static class OnJndiOrProperty extends AllNestedConditions {
*
* &#064;ConditionalOnJndi()
* static class OnJndi {
* }
* &#064;ConditionalOnProperty("something")
* static class OnProperty {
* }
*
* }
* </pre>
*
* @author Phillip Webb
* @since 1.2.0
*/
public abstract class AllNestedConditions extends AbstractNestedCondition {
public AllNestedConditions(ConfigurationPhase configurationPhase) {
super(configurationPhase);
}
@Override
protected ConditionOutcome buildConditionOutcome(List<ConditionOutcome> outcomes) {
List<ConditionOutcome> match = new ArrayList<ConditionOutcome>();
List<ConditionOutcome> nonMatch = new ArrayList<ConditionOutcome>();
for (ConditionOutcome outcome : outcomes) {
if (outcome.isMatch()) {
match.add(outcome);
}
else {
nonMatch.add(outcome);
}
}
return new ConditionOutcome(match.size() == outcomes.size(),
"all match resulted in " + match + " matches and " + nonMatch
+ " non matches");
}
}

@ -16,32 +16,16 @@
package org.springframework.boot.autoconfigure.condition;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* {@link Condition} that will match when any nested class condition matches. Can be used
* to create composite conditions, for example:
* {@link Condition} that will match when any nested class condition matches.
* Can be used to create composite conditions, for example:
*
* <pre class="code">
* static class OnJndiOrProperty extends AnyNestedCondition {
@ -61,122 +45,25 @@ import org.springframework.util.StringUtils;
* @since 1.2.0
*/
@Order(Ordered.LOWEST_PRECEDENCE - 20)
public abstract class AnyNestedCondition extends SpringBootCondition implements
ConfigurationCondition {
private final ConfigurationPhase configurationPhase;
public abstract class AnyNestedCondition extends AbstractNestedCondition {
public AnyNestedCondition(ConfigurationPhase configurationPhase) {
Assert.notNull(configurationPhase, "ConfigurationPhase must not be null");
this.configurationPhase = configurationPhase;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return this.configurationPhase;
super(configurationPhase);
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
MemberConditions memberConditions = new MemberConditions(context, getClass()
.getName());
List<ConditionOutcome> outcomes = memberConditions.getMatchOutcomes();
protected ConditionOutcome buildConditionOutcome(List<ConditionOutcome> outcomes) {
List<ConditionOutcome> match = new ArrayList<ConditionOutcome>();
List<ConditionOutcome> nonMatch = new ArrayList<ConditionOutcome>();
for (ConditionOutcome outcome : outcomes) {
if (outcome.isMatch()) {
match.add(outcome);
}
else {
} else {
nonMatch.add(outcome);
}
}
return new ConditionOutcome(match.size() > 0, "any match resulted in " + match
+ " matches and " + nonMatch + " non matches");
}
private static class MemberConditions {
private final ConditionContext context;
private final MetadataReaderFactory readerFactory;
private final Map<AnnotationMetadata, List<Condition>> memberConditions;
public MemberConditions(ConditionContext context, String className) {
this.context = context;
this.readerFactory = new SimpleMetadataReaderFactory(
context.getResourceLoader());
String[] members = getMetadata(className).getMemberClassNames();
this.memberConditions = getMemberConditions(members);
}
private Map<AnnotationMetadata, List<Condition>> getMemberConditions(
String[] members) {
MultiValueMap<AnnotationMetadata, Condition> memberConditions = new LinkedMultiValueMap<AnnotationMetadata, Condition>();
for (String member : members) {
AnnotationMetadata metadata = getMetadata(member);
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass);
memberConditions.add(metadata, condition);
}
}
}
return Collections.unmodifiableMap(memberConditions);
}
private AnnotationMetadata getMetadata(String className) {
try {
return this.readerFactory.getMetadataReader(className)
.getAnnotationMetadata();
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
@SuppressWarnings("unchecked")
private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(Conditional.class.getName(), true);
Object values = (attributes != null ? attributes.get("value") : null);
return (List<String[]>) (values != null ? values : Collections.emptyList());
}
private Condition getCondition(String conditionClassName) {
Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName,
this.context.getClassLoader());
return (Condition) BeanUtils.instantiateClass(conditionClass);
}
public List<ConditionOutcome> getMatchOutcomes() {
List<ConditionOutcome> outcomes = new ArrayList<ConditionOutcome>();
for (Map.Entry<AnnotationMetadata, List<Condition>> entry : this.memberConditions
.entrySet()) {
AnnotationMetadata metadata = entry.getKey();
for (Condition condition : entry.getValue()) {
outcomes.add(getConditionOutcome(metadata, condition));
}
}
return Collections.unmodifiableList(outcomes);
}
private ConditionOutcome getConditionOutcome(AnnotationMetadata metadata,
Condition condition) {
String messagePrefix = "member condition on " + metadata.getClassName();
if (condition instanceof SpringBootCondition) {
ConditionOutcome outcome = ((SpringBootCondition) condition)
.getMatchOutcome(this.context, metadata);
String message = outcome.getMessage();
return new ConditionOutcome(outcome.isMatch(), messagePrefix
+ (StringUtils.hasLength(message) ? " : " + message : ""));
}
boolean matches = condition.matches(this.context, metadata);
return new ConditionOutcome(matches, messagePrefix);
}
return new ConditionOutcome(match.size() > 0, "any match resulted in " + match + " matches and " + nonMatch
+ " non matches");
}
}

@ -0,0 +1,50 @@
package org.springframework.boot.autoconfigure.condition;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Condition;
/**
* {@link Condition} that will match when none of the nested class conditions match.
* Can be used to create composite conditions, for example:
*
* <pre class="code">
* static class OnJndiOrProperty extends NoneOfNestedConditions {
*
* &#064;ConditionalOnJndi()
* static class OnJndi {
* }
* &#064;ConditionalOnProperty("something")
* static class OnProperty {
* }
*
* }
* </pre>
*
* @author Phillip Webb
* @since 1.2.0
*/
public abstract class NoneOfNestedConditions extends AbstractNestedCondition {
public NoneOfNestedConditions(ConfigurationPhase configurationPhase) {
super(configurationPhase);
}
@Override
protected ConditionOutcome buildConditionOutcome(List<ConditionOutcome> outcomes) {
List<ConditionOutcome> match = new ArrayList<ConditionOutcome>();
List<ConditionOutcome> nonMatch = new ArrayList<ConditionOutcome>();
for (ConditionOutcome outcome : outcomes) {
if (outcome.isMatch()) {
match.add(outcome);
} else {
nonMatch.add(outcome);
}
}
return new ConditionOutcome(match.size() == 0, "none of match resulted in " + match + " matches and "
+ nonMatch + " non matches");
}
}

@ -0,0 +1,99 @@
/*
* Copyright 2012-2014 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 static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Tests for {@link AllNestedConditions}.
*/
public class AllNestedConditionsTests {
@Test
public void neither() throws Exception {
AnnotationConfigApplicationContext context = load(Config.class);
assertThat(context.containsBean("myBean"), equalTo(false));
context.close();
}
@Test
public void propertyA() throws Exception {
AnnotationConfigApplicationContext context = load(Config.class, "a:a");
assertThat(context.containsBean("myBean"), equalTo(false));
context.close();
}
@Test
public void propertyB() throws Exception {
AnnotationConfigApplicationContext context = load(Config.class, "b:b");
assertThat(context.containsBean("myBean"), equalTo(false));
context.close();
}
@Test
public void both() throws Exception {
AnnotationConfigApplicationContext context = load(Config.class, "a:a", "b:b");
assertThat(context.containsBean("myBean"), equalTo(true));
context.close();
}
private AnnotationConfigApplicationContext load(Class<?> config, String... env) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(context, env);
context.register(config);
context.refresh();
return context;
}
@Configuration
@Conditional(OnPropertyAAndBCondition.class)
public static class Config {
@Bean
public String myBean() {
return "myBean";
}
}
static class OnPropertyAAndBCondition extends AllNestedConditions {
public OnPropertyAAndBCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty("a")
static class HasPropertyA {
}
@ConditionalOnProperty("b")
static class HasPropertyB {
}
}
}

@ -0,0 +1,99 @@
/*
* Copyright 2012-2014 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 static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Tests for {@link NoneOfNestedConditions}.
*/
public class NoneOfNestedConditionsTests {
@Test
public void neither() throws Exception {
AnnotationConfigApplicationContext context = load(Config.class);
assertThat(context.containsBean("myBean"), equalTo(true));
context.close();
}
@Test
public void propertyA() throws Exception {
AnnotationConfigApplicationContext context = load(Config.class, "a:a");
assertThat(context.containsBean("myBean"), equalTo(false));
context.close();
}
@Test
public void propertyB() throws Exception {
AnnotationConfigApplicationContext context = load(Config.class, "b:b");
assertThat(context.containsBean("myBean"), equalTo(false));
context.close();
}
@Test
public void both() throws Exception {
AnnotationConfigApplicationContext context = load(Config.class, "a:a", "b:b");
assertThat(context.containsBean("myBean"), equalTo(false));
context.close();
}
private AnnotationConfigApplicationContext load(Class<?> config, String... env) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(context, env);
context.register(config);
context.refresh();
return context;
}
@Configuration
@Conditional(NeitherPropertyANorPropertyBCondition.class)
public static class Config {
@Bean
public String myBean() {
return "myBean";
}
}
static class NeitherPropertyANorPropertyBCondition extends NoneOfNestedConditions {
public NeitherPropertyANorPropertyBCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty("a")
static class HasPropertyA {
}
@ConditionalOnProperty("b")
static class HasPropertyB {
}
}
}
Loading…
Cancel
Save