Commit 7396ccfe authored by Phillip Webb's avatar Phillip Webb

Harmonize ConditionOutcome messages

Add ConditionMessage class to help build condition messages in a
uniform format and update existing conditions to use it.

Fixes gh-6756
parent 41dc53f5
......@@ -26,6 +26,7 @@ import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
......@@ -92,8 +93,13 @@ public class EndpointMBeanExportAutoConfiguration {
AnnotatedTypeMetadata metadata) {
boolean jmxEnabled = isEnabled(context, "spring.jmx.");
boolean jmxEndpointsEnabled = isEnabled(context, "endpoints.jmx.");
return new ConditionOutcome(jmxEnabled && jmxEndpointsEnabled,
"JMX Endpoints");
if (jmxEnabled && jmxEndpointsEnabled) {
return ConditionOutcome.match(
ConditionMessage.forCondition("JMX Enabled").found("properties")
.items("spring.jmx.enabled", "endpoints.jmx.enabled"));
}
return ConditionOutcome.noMatch(ConditionMessage.forCondition("JMX Enabled")
.because("spring.jmx.enabled or endpoints.jmx.enabled is not set"));
}
private boolean isEnabled(ConditionContext context, String prefix) {
......
......@@ -40,6 +40,7 @@ import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
......@@ -333,13 +334,18 @@ public class EndpointWebMvcAutoConfiguration
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Management Server MVC");
if (!(context.getResourceLoader() instanceof WebApplicationContext)) {
return ConditionOutcome.noMatch("Non WebApplicationContext");
return ConditionOutcome
.noMatch(message.because("non WebApplicationContext"));
}
ManagementServerPort port = ManagementServerPort.get(context.getEnvironment(),
context.getBeanFactory());
return new ConditionOutcome(port == ManagementServerPort.SAME,
"Management context");
if (port == ManagementServerPort.SAME) {
return ConditionOutcome.match(message.because("port is same"));
}
return ConditionOutcome.noMatch(message.because("port is not same"));
}
}
......
......@@ -37,6 +37,7 @@ import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
......@@ -182,20 +183,22 @@ public class EndpointWebMvcManagementContextConfiguration {
AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String config = environment.resolvePlaceholders("${logging.file:}");
ConditionMessage.Builder message = ConditionMessage
.forCondition("Log File");
if (StringUtils.hasText(config)) {
return ConditionOutcome.match("Found logging.file: " + config);
return ConditionOutcome.match(message.found("logging.file").items(config));
}
config = environment.resolvePlaceholders("${logging.path:}");
if (StringUtils.hasText(config)) {
return ConditionOutcome.match("Found logging.path: " + config);
return ConditionOutcome.match(message.found("logging.path").items(config));
}
config = new RelaxedPropertyResolver(environment, "endpoints.logfile.")
.getProperty("external-file");
if (StringUtils.hasText(config)) {
return ConditionOutcome
.match("Found endpoints.logfile.external-file: " + config);
return ConditionOutcome.match(
message.found("endpoints.logfile.external-file").items(config));
}
return ConditionOutcome.noMatch("Found no log file configuration");
return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll());
}
}
......
......@@ -25,6 +25,7 @@ import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
......@@ -97,8 +98,12 @@ public class JolokiaAutoConfiguration {
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
boolean endpointsEnabled = isEnabled(context, "endpoints.", true);
boolean enabled = isEnabled(context, "endpoints.jolokia.", endpointsEnabled);
return new ConditionOutcome(enabled, "Jolokia enabled");
ConditionMessage.Builder message = ConditionMessage
.forCondition("Jolokia");
if (isEnabled(context, "endpoints.jolokia.", endpointsEnabled)) {
return ConditionOutcome.match(message.because("enabled"));
}
return ConditionOutcome.noMatch(message.because("not enabled"));
}
private boolean isEnabled(ConditionContext context, String prefix,
......
......@@ -33,6 +33,7 @@ import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
......@@ -223,10 +224,13 @@ public class ManagementWebSecurityAutoConfiguration {
.getProperty("management.security.enabled", "true");
String basicEnabled = context.getEnvironment()
.getProperty("security.basic.enabled", "true");
return new ConditionOutcome(
"true".equalsIgnoreCase(managementEnabled)
&& !"true".equalsIgnoreCase(basicEnabled),
"Management security enabled and basic disabled");
ConditionMessage.Builder message = ConditionMessage
.forCondition("WebSecurityEnabled");
if ("true".equalsIgnoreCase(managementEnabled)
&& !"true".equalsIgnoreCase(basicEnabled)) {
return ConditionOutcome.match(message.because("security enabled"));
}
return ConditionOutcome.noMatch(message.because("security disabled"));
}
}
......
......@@ -16,6 +16,9 @@
package org.springframework.boot.actuate.autoconfigure;
import java.lang.annotation.Annotation;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.bind.RelaxedPropertyResolver;
......@@ -33,9 +36,10 @@ abstract class OnEnabledEndpointElementCondition extends SpringBootCondition {
private final String prefix;
private final Class<?> annotationType;
private final Class<? extends Annotation> annotationType;
OnEnabledEndpointElementCondition(String prefix, Class<?> annotationType) {
OnEnabledEndpointElementCondition(String prefix,
Class<? extends Annotation> annotationType) {
this.prefix = prefix;
this.annotationType = annotationType;
}
......@@ -69,7 +73,8 @@ abstract class OnEnabledEndpointElementCondition extends SpringBootCondition {
if (resolver.containsProperty("enabled")) {
boolean match = resolver.getProperty("enabled", Boolean.class, true);
return new ConditionOutcome(match,
getEndpointElementOutcomeMessage(endpointName, match));
ConditionMessage.forCondition(this.annotationType).because(
this.prefix + endpointName + ".enabled is " + match));
}
return null;
}
......@@ -79,7 +84,8 @@ abstract class OnEnabledEndpointElementCondition extends SpringBootCondition {
context.getEnvironment(), this.prefix + "defaults.");
boolean match = Boolean.valueOf(resolver.getProperty("enabled", "true"));
return new ConditionOutcome(match,
getDefaultEndpointElementOutcomeMessage(match));
ConditionMessage.forCondition(this.annotationType).because(
this.prefix + "defaults.enabled is considered " + match));
}
}
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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,7 @@
package org.springframework.boot.actuate.condition;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.bind.RelaxedPropertyResolver;
......@@ -31,14 +32,11 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
*/
class OnEnabledEndpointCondition extends SpringBootCondition {
private static final String ANNOTATION_CLASS = ConditionalOnEnabledEndpoint.class
.getName();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
AnnotationAttributes annotationAttributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(ANNOTATION_CLASS));
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(metadata
.getAnnotationAttributes(ConditionalOnEnabledEndpoint.class.getName()));
String endpointName = annotationAttributes.getString("value");
boolean enabledByDefault = annotationAttributes.getBoolean("enabledByDefault");
ConditionOutcome outcome = determineEndpointOutcome(endpointName,
......@@ -56,8 +54,11 @@ class OnEnabledEndpointCondition extends SpringBootCondition {
if (resolver.containsProperty("enabled") || !enabledByDefault) {
boolean match = resolver.getProperty("enabled", Boolean.class,
enabledByDefault);
return new ConditionOutcome(match, "The endpoint " + endpointName + " is "
+ (match ? "enabled" : "disabled"));
ConditionMessage message = ConditionMessage
.forCondition(ConditionalOnEnabledEndpoint.class,
"(" + endpointName + ")")
.because(match ? "enabled" : "disabled");
return new ConditionOutcome(match, message);
}
return null;
}
......@@ -66,8 +67,11 @@ class OnEnabledEndpointCondition extends SpringBootCondition {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "endpoints.");
boolean match = Boolean.valueOf(resolver.getProperty("enabled", "true"));
return new ConditionOutcome(match,
"All endpoints are " + (match ? "enabled" : "disabled") + " by default");
ConditionMessage message = ConditionMessage
.forCondition(ConditionalOnEnabledEndpoint.class)
.because("All endpoints are " + (match ? "enabled" : "disabled")
+ " by default");
return new ConditionOutcome(match, message);
}
}
......@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure;
import java.nio.charset.Charset;
import org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration.ResourceBundleCondition;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
......@@ -161,17 +162,19 @@ public class MessageSourceAutoConfiguration {
private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context,
String basename) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("ResourceBundle");
for (String name : StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(basename))) {
for (Resource resource : getResources(context.getClassLoader(), name)) {
if (resource.exists()) {
return ConditionOutcome.match("Bundle found for "
+ "spring.messages.basename: " + name);
return ConditionOutcome
.match(message.found("bundle").items(resource));
}
}
}
return ConditionOutcome.noMatch(
"No bundle found for " + "spring.messages.basename: " + basename);
message.didNotFind("bundle with basename " + basename).atAll());
}
private Resource[] getResources(ClassLoader classLoader, String name) {
......
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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,12 +16,14 @@
package org.springframework.boot.autoconfigure.cache;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
/**
* General cache condition used with all cache configuration classes.
......@@ -35,18 +37,24 @@ class CacheCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String sourceClass = "";
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata) metadata).getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache",
sourceClass);
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "spring.cache.");
if (!resolver.containsProperty("type")) {
return ConditionOutcome.match("Automatic cache type");
return ConditionOutcome.match(message.because("automatic cache type"));
}
CacheType cacheType = CacheConfigurations
.getType(((AnnotationMetadata) metadata).getClassName());
String value = resolver.getProperty("type").replace("-", "_").toUpperCase();
if (value.equals(cacheType.name())) {
return ConditionOutcome.match("Cache type " + cacheType);
return ConditionOutcome.match(message.because(value + " cache type"));
}
return ConditionOutcome.noMatch("Cache type " + value);
return ConditionOutcome.noMatch(message.because(value + " cache type"));
}
}
......@@ -28,6 +28,7 @@ import javax.cache.spi.CachingProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
......@@ -177,23 +178,28 @@ class JCacheCacheConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("JCache");
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "spring.cache.jcache.");
if (resolver.containsProperty("provider")) {
return ConditionOutcome.match("JCache provider specified");
return ConditionOutcome
.match(message.because("JCache provider specified"));
}
Iterator<CachingProvider> providers = Caching.getCachingProviders()
.iterator();
if (!providers.hasNext()) {
return ConditionOutcome.noMatch("No JSR-107 compliant providers");
return ConditionOutcome
.noMatch(message.didNotFind("JSR-107 provider").atAll());
}
providers.next();
if (providers.hasNext()) {
return ConditionOutcome.noMatch(
"Multiple default JSR-107 compliant " + "providers found");
return ConditionOutcome
.noMatch(message.foundExactly("multiple JSR-107 providers"));
}
return ConditionOutcome.match("Default JSR-107 compliant provider found.");
return ConditionOutcome
.match(message.foundExactly("single JSR-107 provider"));
}
}
......
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
......@@ -35,7 +35,6 @@ 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;
/**
* Abstract base class for nested conditions.
......@@ -171,16 +170,18 @@ abstract class AbstractNestedCondition extends SpringBootCondition
private ConditionOutcome getConditionOutcome(AnnotationMetadata metadata,
Condition condition) {
String messagePrefix = "member condition on " + metadata.getClassName();
String className = ClassUtils.getShortName(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 : ""));
ConditionMessage message = outcome.getConditionMessage()
.append("on member " + className);
return new ConditionOutcome(outcome.isMatch(), message);
}
boolean matches = condition.matches(this.context, metadata);
return new ConditionOutcome(matches, messagePrefix);
return new ConditionOutcome(matches,
ConditionMessage.forCondition("NestedCondition")
.because("nested on member " + className));
}
}
......
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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,9 @@
package org.springframework.boot.autoconfigure.condition;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Condition;
/**
......@@ -47,11 +50,19 @@ public abstract class AllNestedConditions extends AbstractNestedCondition {
@Override
protected ConditionOutcome getFinalMatchOutcome(MemberMatchOutcomes memberOutcomes) {
return new ConditionOutcome(
memberOutcomes.getMatches().size() == memberOutcomes.getAll().size(),
"nested all match resulted in " + memberOutcomes.getMatches()
+ " matches and " + memberOutcomes.getNonMatches()
+ " non matches");
boolean match = hasSameSize(memberOutcomes.getMatches(), memberOutcomes.getAll());
List<ConditionMessage> messages = new ArrayList<ConditionMessage>();
messages.add(ConditionMessage.forCondition("AllNestedConditions")
.because(memberOutcomes.getMatches().size() + " matched "
+ memberOutcomes.getNonMatches().size() + " did not"));
for (ConditionOutcome outcome : memberOutcomes.getAll()) {
messages.add(outcome.getConditionMessage());
}
return new ConditionOutcome(match, ConditionMessage.of(messages));
}
private boolean hasSameSize(List<?> list1, List<?> list2) {
return list1.size() == list2.size();
}
}
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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,9 @@
package org.springframework.boot.autoconfigure.condition;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Condition;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
......@@ -50,11 +53,15 @@ public abstract class AnyNestedCondition extends AbstractNestedCondition {
@Override
protected ConditionOutcome getFinalMatchOutcome(MemberMatchOutcomes memberOutcomes) {
return new ConditionOutcome(!memberOutcomes.getMatches().isEmpty(),
"nested any match resulted in " + memberOutcomes.getMatches()
+ " matches and " + memberOutcomes.getNonMatches()
+ " non matches");
boolean match = !memberOutcomes.getMatches().isEmpty();
List<ConditionMessage> messages = new ArrayList<ConditionMessage>();
messages.add(ConditionMessage.forCondition("AnyNestedCondition")
.because(memberOutcomes.getMatches().size() + " matched "
+ memberOutcomes.getNonMatches().size() + " did not"));
for (ConditionOutcome outcome : memberOutcomes.getAll()) {
messages.add(outcome.getConditionMessage());
}
return new ConditionOutcome(match, ConditionMessage.of(messages));
}
}
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
......@@ -127,8 +127,8 @@ public final class ConditionEvaluationReport {
String prefix = source + "$";
for (Entry<String, ConditionAndOutcomes> entry : this.outcomes.entrySet()) {
if (entry.getKey().startsWith(prefix)) {
ConditionOutcome outcome = new ConditionOutcome(false,
"Ancestor '" + source + "' did not match");
ConditionOutcome outcome = ConditionOutcome.noMatch(ConditionMessage
.forCondition("Ancestor " + source).because("did not match"));
entry.getValue().add(ANCESTOR_CONDITION, outcome);
}
}
......
/*
* Copyright 2012-2016 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.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* A message associated with a {@link ConditionOutcome}. Provides a fluent builder style
* API to encourage consistency across all condition messages.
*
* @author Phillip Webb
* @since 1.4.1
*/
public final class ConditionMessage {
private String message;
private ConditionMessage() {
this(null);
}
private ConditionMessage(String message) {
this.message = message;
}
private ConditionMessage(ConditionMessage prior, String message) {
this.message = (prior.isEmpty() ? message : prior + "; " + message);
}
/**
* Return {@code true} if the message is empty.
* @return if the message is empty
*/
public boolean isEmpty() {
return !StringUtils.hasLength(this.message);
}
@Override
public String toString() {
return (this.message == null ? "" : this.message);
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.message);
}
@Override
public boolean equals(Object obj) {
if (obj == null || !ConditionMessage.class.isInstance(obj)) {
return false;
}
if (obj == this) {
return true;
}
return ObjectUtils.nullSafeEquals(((ConditionMessage) obj).message, this.message);
}
/**
* Return a new {@link ConditionMessage} based on the instance and an appended
* message.
* @param message the message to append
* @return a new {@link ConditionMessage} instance
*/
public ConditionMessage append(String message) {
if (!StringUtils.hasLength(message)) {
return this;
}
if (!StringUtils.hasLength(this.message)) {
return new ConditionMessage(message);
}
return new ConditionMessage(this.message + " " + message);
}
/**
* Return a new builder to construct a new {@link ConditionMessage} based on the
* instance and a new condition outcome.
* @param condition the condition
* @param details details of the condition
* @return a {@link Builder} builder
* @see #andCondition(String, Object...)
* @see #forCondition(Class, Object...)
*/
public Builder andCondition(Class<? extends Annotation> condition,
Object... details) {
Assert.notNull(condition, "Condition must not be null");
return andCondition("@" + ClassUtils.getShortName(condition), details);
}
/**
* Return a new builder to construct a new {@link ConditionMessage} based on the
* instance and a new condition outcome.
* @param condition the condition
* @param details details of the condition
* @return a {@link Builder} builder
* @see #andCondition(Class, Object...)
* @see #forCondition(String, Object...)
*/
public Builder andCondition(String condition, Object... details) {
Assert.notNull(condition, "Condition must not be null");
String detail = StringUtils.arrayToDelimitedString(details, " ");
if (StringUtils.hasLength(detail)) {
return new Builder(condition + " " + detail);
}
return new Builder(condition);
}
/**
* Factory method to return a new empty {@link ConditionMessage}.
* @return a new empty {@link ConditionMessage}
*/
public static ConditionMessage empty() {
return new ConditionMessage();
}
/**
* Factory method to create a new {@link ConditionMessage} with a specific message.
* @param message the source message (may be a format string if {@code args} are
* specified
* @param args format arguments for the message
* @return a new {@link ConditionMessage} instance
*/
public static ConditionMessage of(String message, Object... args) {
if (ObjectUtils.isEmpty(args)) {
return new ConditionMessage(message);
}
return new ConditionMessage(String.format(message, args));
}
/**
* Factory method to create a new {@link ConditionMessage} comprised of the specified
* messages.
* @param messages the source messages (may be {@code null}
* @return a new {@link ConditionMessage} instance
*/
public static ConditionMessage of(Collection<? extends ConditionMessage> messages) {
ConditionMessage result = new ConditionMessage();
if (messages != null) {
for (ConditionMessage message : messages) {
result = new ConditionMessage(result, message.toString());
}
}
return result;
}
/**
* Factory method for a builder to construct a new {@link ConditionMessage} for a
* condition.
* @param condition the condition
* @param details details of the condition
* @return a {@link Builder} builder
* @see #forCondition(String, Object...)
* @see #andCondition(String, Object...)
*/
public static Builder forCondition(Class<? extends Annotation> condition,
Object... details) {
return new ConditionMessage().andCondition(condition, details);
}
/**
* Factory method for a builder to construct a new {@link ConditionMessage} for a
* condition.
* @param condition the condition
* @param details details of the condition
* @return a {@link Builder} builder
* @see #forCondition(Class, Object...)
* @see #andCondition(String, Object...)
*/
public static Builder forCondition(String condition, Object... details) {
return new ConditionMessage().andCondition(condition, details);
}
/**
* Builder used to create a {@link ConditionMessage} for a condition.
*/
public final class Builder {
private final String condition;
private Builder(String condition) {
this.condition = condition;
}
/**
* Indicate that an exact result was found. For example
* {@code foundExactly("foo")} results in the message "found foo".
* @param result the result that was found
* @return a built {@link ConditionMessage}
*/
public ConditionMessage foundExactly(Object result) {
return found("").items(result);
}
/**
* Indicate that one or more results were found. For example
* {@code found("bean").items("x")} results in the message "found bean x".
* @param article the article found
* @return an {@link ItemsBuilder}
*/
public ItemsBuilder found(String article) {
return found(article, article);
}
/**
* Indicate that one or more results were found. For example
* {@code found("bean", "beans").items("x", "y")} results in the message
* "found beans x, y".
* @param singular the article found in singular form
* @param plural the article found in plural form
* @return an {@link ItemsBuilder}
*/
public ItemsBuilder found(String singular, String plural) {
return new ItemsBuilder(this, "found", singular, plural);
}
/**
* Indicate that one or more results were not found. For example
* {@code didNotFind("bean").items("x")} results in the message
* "did not find bean x".
* @param article the article found
* @return an {@link ItemsBuilder}
*/
public ItemsBuilder didNotFind(String article) {
return didNotFind(article, article);
}
/**
* Indicate that one or more results were found. For example
* {@code didNotFind("bean", "beans").items("x", "y")} results in the message
* "did not find beans x, y".
* @param singular the article found in singular form
* @param plural the article found in plural form
* @return an {@link ItemsBuilder}
*/
public ItemsBuilder didNotFind(String singular, String plural) {
return new ItemsBuilder(this, "did not find", singular, plural);
}
/**
* Indicates a single result. For example {@code resultedIn("yes")} results in the
* message "resulted in yes".
* @param result the result
* @return a built {@link ConditionMessage}
*/
public ConditionMessage resultedIn(Object result) {
return because("resulted in " + result);
}
/**
* Indicates something is available. For example {@code available("money")}
* results in the message "money is available".
* @param item the item that is available
* @return a built {@link ConditionMessage}
*/
public ConditionMessage available(String item) {
return because(item + " is available");
}
/**
* Indicates something is not available. For example {@code notAvailable("time")}
* results in the message "time in not available".
* @param item the item that is not available
* @return a built {@link ConditionMessage}
*/
public ConditionMessage notAvailable(String item) {
return because(item + " is not available");
}
/**
* Indicates the reason. For example {@code reason("running Linux")} results in
* the message "running Linux".
* @param reason the reason for the message
* @return a built {@link ConditionMessage}
*/
public ConditionMessage because(String reason) {
if (StringUtils.isEmpty(reason)) {
return new ConditionMessage(ConditionMessage.this, this.condition);
}
return new ConditionMessage(ConditionMessage.this,
this.condition + " " + reason);
}
}
/**
* Builder used to create a {@link ItemsBuilder} for a condition.
*/
public final class ItemsBuilder {
private final Builder condition;
private final String reson;
private final String singular;
private final String plural;
private ItemsBuilder(Builder condition, String reason, String singular,
String plural) {
this.condition = condition;
this.reson = reason;
this.singular = singular;
this.plural = plural;
}
/**
* Used when no items are available. For example
* {@code didNotFind("any beans").atAll()} results in the message
* "did not find any beans".
* @return a built {@link ConditionMessage}
*/
public ConditionMessage atAll() {
return items(Collections.emptyList());
}
/**
* Indicate the items. For example
* {@code didNotFind("bean", "beans").items("x", "y")} results in the message
* "did not find beans x, y".
* @param items the items (may be {@code null})
* @return a built {@link ConditionMessage}
*/
public ConditionMessage items(Object... items) {
return items(Style.NORMAL, items);
}
/**
* Indicate the items. For example
* {@code didNotFind("bean", "beans").items("x", "y")} results in the message
* "did not find beans x, y".
* @param style the render style
* @param items the items (may be {@code null})
* @return a built {@link ConditionMessage}
*/
public ConditionMessage items(Style style, Object... items) {
return items(style,
items == null ? (Collection<?>) null : Arrays.asList(items));
}
/**
* Indicate the items. For example
* {@code didNotFind("bean", "beans").items(Collections.singleton("x")} results in
* the message "did not find bean x".
* @param items the source of the items (may be {@code null})
* @return a built {@link ConditionMessage}
*/
public ConditionMessage items(Collection<?> items) {
return items(Style.NORMAL, items);
}
/**
* Indicate the items. For example
* {@code didNotFind("bean", "beans").items(Collections.singleton("x")} results in
* the message "did not find bean x".
* @param style the render style
* @param items the source of the items (may be {@code null})
* @return a built {@link ConditionMessage}
*/
public ConditionMessage items(Style style, Collection<?> items) {
Assert.notNull(style, "Style must not be null");
StringBuilder message = new StringBuilder(this.reson);
items = style.applyTo(items);
if ((this.condition == null || items.size() <= 1)
&& StringUtils.hasLength(this.singular)) {
message.append(" " + this.singular);
}
else if (StringUtils.hasLength(this.plural)) {
message.append(" " + this.plural);
}
if (items != null && !items.isEmpty()) {
message.append(
" " + StringUtils.collectionToDelimitedString(items, ", "));
}
return this.condition.because(message.toString());
}
}
/**
* Render styles.
*/
public enum Style {
NORMAL {
@Override
protected Object applyToItem(Object item) {
return item;
}
},
QUOTE {
@Override
protected String applyToItem(Object item) {
return (item == null ? null : "'" + item + "'");
}
};
public Collection<?> applyTo(Collection<?> items) {
List<Object> result = new ArrayList<Object>();
for (Object item : items) {
result.add(applyToItem(item));
}
return result;
}
protected abstract Object applyToItem(Object item);
}
}
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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,20 +16,38 @@
package org.springframework.boot.autoconfigure.condition;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Outcome for a condition match, including log message.
*
* @author Phillip Webb
* @see ConditionMessage
*/
public class ConditionOutcome {
private final boolean match;
private final String message;
private final ConditionMessage message;
/**
* Create a new {@link ConditionOutcome} instance. For more consistent messages
* consider using {@link #ConditionOutcome(boolean, ConditionMessage)}.
* @param match if the condition is a match
* @param message the condition message
*/
public ConditionOutcome(boolean match, String message) {
this(match, ConditionMessage.of(message));
}
/**
* Create a new {@link ConditionOutcome} instance.
* @param match if the condition is a match
* @param message the condition message
*/
public ConditionOutcome(boolean match, ConditionMessage message) {
Assert.notNull(message, "ConditionMessage must not be null");
this.match = match;
this.message = message;
}
......@@ -39,11 +57,12 @@ public class ConditionOutcome {
* @return the {@link ConditionOutcome}
*/
public static ConditionOutcome match() {
return match(null);
return match(ConditionMessage.empty());
}
/**
* Create a new {@link ConditionOutcome} instance for 'match'.
* Create a new {@link ConditionOutcome} instance for 'match'. For more consistent
* messages consider using {@link #match(ConditionMessage)}.
* @param message the message
* @return the {@link ConditionOutcome}
*/
......@@ -52,7 +71,17 @@ public class ConditionOutcome {
}
/**
* Create a new {@link ConditionOutcome} instance for 'no match'.
* Create a new {@link ConditionOutcome} instance for 'match'.
* @param message the message
* @return the {@link ConditionOutcome}
*/
public static ConditionOutcome match(ConditionMessage message) {
return new ConditionOutcome(true, message);
}
/**
* Create a new {@link ConditionOutcome} instance for 'no match'. For more consistent
* messages consider using {@link #noMatch(ConditionMessage)}.
* @param message the message
* @return the {@link ConditionOutcome}
*/
......@@ -60,6 +89,15 @@ public class ConditionOutcome {
return new ConditionOutcome(false, message);
}
/**
* Create a new {@link ConditionOutcome} instance for 'no match'.
* @param message the message
* @return the {@link ConditionOutcome}
*/
public static ConditionOutcome noMatch(ConditionMessage message) {
return new ConditionOutcome(false, message);
}
/**
* Return {@code true} if the outcome was a match.
* @return {@code true} if the outcome matches
......@@ -73,6 +111,14 @@ public class ConditionOutcome {
* @return the message or {@code null}
*/
public String getMessage() {
return (this.message.isEmpty() ? null : this.message.toString());
}
/**
* Return an outcome message or {@code null}.
* @return the message or {@code null}
*/
public ConditionMessage getConditionMessage() {
return this.message;
}
......@@ -100,7 +146,7 @@ public class ConditionOutcome {
@Override
public String toString() {
return (this.message == null ? "" : this.message);
return (this.message == null ? "" : this.message.toString());
}
/**
......@@ -110,7 +156,7 @@ public class ConditionOutcome {
* @since 1.3.0
*/
public static ConditionOutcome inverse(ConditionOutcome outcome) {
return new ConditionOutcome(!outcome.isMatch(), outcome.getMessage());
return new ConditionOutcome(!outcome.isMatch(), outcome.getConditionMessage());
}
}
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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,9 @@
package org.springframework.boot.autoconfigure.condition;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Condition;
/**
......@@ -47,10 +50,15 @@ public abstract class NoneNestedConditions extends AbstractNestedCondition {
@Override
protected ConditionOutcome getFinalMatchOutcome(MemberMatchOutcomes memberOutcomes) {
return new ConditionOutcome(memberOutcomes.getMatches().isEmpty(),
"nested none match resulted in " + memberOutcomes.getMatches()
+ " matches and " + memberOutcomes.getNonMatches()
+ " non matches");
boolean match = memberOutcomes.getMatches().isEmpty();
List<ConditionMessage> messages = new ArrayList<ConditionMessage>();
messages.add(ConditionMessage.forCondition("NoneNestedConditions")
.because(memberOutcomes.getMatches().size() + " matched "
+ memberOutcomes.getNonMatches().size() + " did not"));
for (ConditionOutcome outcome : memberOutcomes.getAll()) {
messages.add(outcome.getConditionMessage());
}
return new ConditionOutcome(match, ConditionMessage.of(messages));
}
}
......@@ -31,6 +31,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.condition.ConditionMessage.Style;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
......@@ -74,49 +75,52 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
StringBuilder matchMessage = new StringBuilder();
ConditionMessage matchMessage = ConditionMessage.empty();
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnBean.class);
List<String> matching = getMatchingBeans(context, spec);
if (matching.isEmpty()) {
return ConditionOutcome
.noMatch("@ConditionalOnBean " + spec + " found no beans");
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnBean.class, spec)
.didNotFind("any beans").atAll());
}
matchMessage.append("@ConditionalOnBean ").append(spec)
.append(" found the following ").append(matching);
matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
.found("bean", "beans").items(Style.QUOTE, matching);
}
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,
ConditionalOnSingleCandidate.class);
List<String> matching = getMatchingBeans(context, spec);
if (matching.isEmpty()) {
return ConditionOutcome.noMatch(
"@ConditionalOnSingleCandidate " + spec + " found no beans");
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnSingleCandidate.class, spec)
.didNotFind("any beans").atAll());
}
else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matching,
spec.getStrategy() == SearchStrategy.ALL)) {
return ConditionOutcome.noMatch("@ConditionalOnSingleCandidate " + spec
+ " found no primary candidate amongst the" + " following "
+ matching);
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnSingleCandidate.class, spec)
.didNotFind("a primary bean from beans")
.items(Style.QUOTE, matching));
}
matchMessage.append("@ConditionalOnSingleCandidate ").append(spec)
.append(" found a primary candidate amongst the following ")
.append(matching);
matchMessage = matchMessage
.andCondition(ConditionalOnSingleCandidate.class, spec)
.found("a primary bean from beans").items(Style.QUOTE, matching);
}
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnMissingBean.class);
List<String> matching = getMatchingBeans(context, spec);
if (!matching.isEmpty()) {
return ConditionOutcome.noMatch("@ConditionalOnMissingBean " + spec
+ " found the following " + matching);
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnMissingBean.class, spec)
.found("bean", "beans").items(Style.QUOTE, matching));
}
matchMessage.append(matchMessage.length() == 0 ? "" : " ");
matchMessage.append("@ConditionalOnMissingBean ").append(spec)
.append(" found no beans");
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
.didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage.toString());
return ConditionOutcome.match(matchMessage);
}
private List<String> getMatchingBeans(ConditionContext context,
......
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
......@@ -21,6 +21,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
......@@ -28,7 +29,6 @@ import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* {@link Condition} that checks for the presence or absence of specific classes.
......@@ -43,9 +43,7 @@ class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
StringBuilder matchMessage = new StringBuilder();
ConditionMessage matchMessage = ConditionMessage.empty();
MultiValueMap<String, Object> onClasses = getAttributes(metadata,
ConditionalOnClass.class);
if (onClasses != null) {
......@@ -53,32 +51,31 @@ class OnClassCondition extends SpringBootCondition {
context);
if (!missing.isEmpty()) {
return ConditionOutcome
.noMatch("required @ConditionalOnClass classes not found: "
+ StringUtils.collectionToCommaDelimitedString(missing));
.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes")
.items(Style.QUOTE, missing));
}
matchMessage.append("@ConditionalOnClass classes found: ")
.append(StringUtils.collectionToCommaDelimitedString(
getMatchingClasses(onClasses, MatchType.PRESENT, context)));
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes").items(Style.QUOTE,
getMatchingClasses(onClasses, MatchType.PRESENT, context));
}
MultiValueMap<String, Object> onMissingClasses = getAttributes(metadata,
ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
List<String> present = getMatchingClasses(onMissingClasses, MatchType.PRESENT,
context);
if (!present.isEmpty()) {
return ConditionOutcome
.noMatch("required @ConditionalOnMissing classes found: "
+ StringUtils.collectionToCommaDelimitedString(present));
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnMissingClass.class)
.found("unwanted class", "unwanted classes")
.items(Style.QUOTE, present));
}
matchMessage.append(matchMessage.length() == 0 ? "" : " ");
matchMessage.append("@ConditionalOnMissing classes not found: ")
.append(StringUtils.collectionToCommaDelimitedString(
getMatchingClasses(onMissingClasses, MatchType.MISSING,
context)));
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes")
.items(Style.QUOTE, getMatchingClasses(onMissingClasses,
MatchType.MISSING, context));
}
return ConditionOutcome.match(matchMessage.toString());
return ConditionOutcome.match(matchMessage);
}
private MultiValueMap<String, Object> getAttributes(AnnotatedTypeMetadata metadata,
......@@ -111,17 +108,21 @@ class OnClassCondition extends SpringBootCondition {
private enum MatchType {
PRESENT {
@Override
public boolean matches(String className, ConditionContext context) {
return ClassUtils.isPresent(className, context.getClassLoader());
}
},
MISSING {
@Override
public boolean matches(String className, ConditionContext context) {
return !ClassUtils.isPresent(className, context.getClassLoader());
}
};
public abstract boolean matches(String className, ConditionContext context);
......
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2016 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.
......@@ -24,7 +24,6 @@ import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.ClassMetadata;
/**
* A Condition that evaluates a SpEL expression.
......@@ -38,17 +37,11 @@ class OnExpressionCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String expression = (String) metadata
.getAnnotationAttributes(ConditionalOnExpression.class.getName())
.get("value");
expression = wrapIfNecessary(expression);
String rawExpression = expression;
if (!expression.startsWith("#{")) {
// For convenience allow user to provide bare expression with no #{} wrapper
expression = "#{" + expression + "}";
}
// Explicitly allow environment placeholders inside the expression
expression = context.getEnvironment().resolvePlaceholders(expression);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
BeanExpressionResolver resolver = (beanFactory != null)
......@@ -59,13 +52,21 @@ class OnExpressionCondition extends SpringBootCondition {
resolver = new StandardBeanExpressionResolver();
}
boolean result = (Boolean) resolver.evaluate(expression, expressionContext);
return new ConditionOutcome(result, ConditionMessage
.forCondition(ConditionalOnExpression.class, "(" + rawExpression + ")")
.resultedIn(result));
}
StringBuilder message = new StringBuilder("SpEL expression");
if (metadata instanceof ClassMetadata) {
message.append(" on " + ((ClassMetadata) metadata).getClassName());
/**
* Allow user to provide bare expression with no '#{}' wrapper.
* @param expression source expression
* @return wrapped expression
*/
private String wrapIfNecessary(String expression) {
if (!expression.startsWith("#{")) {
return "#{" + expression + "}";
}
message.append(": " + rawExpression);
return new ConditionOutcome(result, message.toString());
return expression;
}
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2016 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.
......@@ -52,13 +52,13 @@ class OnJavaCondition extends SpringBootCondition {
protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion,
JavaVersion version) {
boolean match = runningVersion.isWithin(range, version);
return new ConditionOutcome(match, getMessage(range, runningVersion, version));
}
private String getMessage(Range range, JavaVersion runningVersion,
JavaVersion version) {
String expected = String.format(
range == Range.EQUAL_OR_NEWER ? "%s or newer" : "older than %s", version);
return "Required JVM version " + expected + " found " + runningVersion;
range == Range.EQUAL_OR_NEWER ? "(%s or newer)" : "(older than %s)",
version);
ConditionMessage message = ConditionMessage
.forCondition(ConditionalOnJava.class, expected)
.foundExactly(runningVersion);
return new ConditionOutcome(match, message);
}
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2016 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.
......@@ -48,26 +48,33 @@ class OnJndiCondition extends SpringBootCondition {
return getMatchOutcome(locations);
}
catch (NoClassDefFoundError ex) {
return ConditionOutcome.noMatch("JNDI class not found");
return ConditionOutcome
.noMatch(ConditionMessage.forCondition(ConditionalOnJndi.class)
.because("JNDI class not found"));
}
}
private ConditionOutcome getMatchOutcome(String[] locations) {
if (!isJndiAvailable()) {
return ConditionOutcome.noMatch("JNDI environment is not available");
return ConditionOutcome
.noMatch(ConditionMessage.forCondition(ConditionalOnJndi.class)
.notAvailable("JNDI environment"));
}
if (locations.length == 0) {
return ConditionOutcome.match("JNDI environment is available");
return ConditionOutcome.match(ConditionMessage
.forCondition(ConditionalOnJndi.class).available("JNDI environment"));
}
JndiLocator locator = getJndiLocator(locations);
String location = locator.lookupFirstLocation();
String details = "(" + StringUtils.arrayToCommaDelimitedString(locations) + ")";
if (location != null) {
return ConditionOutcome
.match("JNDI location '" + location + "' found from candidates "
+ StringUtils.arrayToCommaDelimitedString(locations));
.match(ConditionMessage.forCondition(ConditionalOnJndi.class, details)
.foundExactly("\"" + location + "\""));
}
return ConditionOutcome.noMatch("No JNDI location found from candidates "
+ StringUtils.arrayToCommaDelimitedString(locations));
return ConditionOutcome
.noMatch(ConditionMessage.forCondition(ConditionalOnJndi.class, details)
.didNotFind("any matching JNDI location").atAll());
}
protected boolean isJndiAvailable() {
......
......@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
......@@ -50,12 +51,17 @@ class OnPropertyCondition extends SpringBootCondition {
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(
ConditionalOnProperty.class.getName()));
List<ConditionOutcome> noMatchOutcomes = findNoMatchOutcomes(
allAnnotationAttributes, context.getEnvironment());
if (noMatchOutcomes.isEmpty()) {
return ConditionOutcome.match();
List<ConditionMessage> noMatch = new ArrayList<ConditionMessage>();
List<ConditionMessage> match = new ArrayList<ConditionMessage>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
ConditionOutcome outcome = determineOutcome(annotationAttributes,
context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.noMatch(getCompositeMessage(noMatchOutcomes));
return ConditionOutcome.match(ConditionMessage.of(match));
}
private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
......@@ -82,104 +88,110 @@ class OnPropertyCondition extends SpringBootCondition {
return annotationAttributes;
}
private List<ConditionOutcome> findNoMatchOutcomes(
List<AnnotationAttributes> allAnnotationAttributes,
PropertyResolver resolver) {
List<ConditionOutcome> noMatchOutcomes = new ArrayList<ConditionOutcome>(
allAnnotationAttributes.size());
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
ConditionOutcome outcome = determineOutcome(annotationAttributes, resolver);
if (!outcome.isMatch()) {
noMatchOutcomes.add(outcome);
}
}
return noMatchOutcomes;
}
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes,
PropertyResolver resolver) {
String prefix = annotationAttributes.getString("prefix").trim();
if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
prefix = prefix + ".";
}
String havingValue = annotationAttributes.getString("havingValue");
String[] names = getNames(annotationAttributes);
boolean relaxedNames = annotationAttributes.getBoolean("relaxedNames");
boolean matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
if (relaxedNames) {
resolver = new RelaxedPropertyResolver(resolver, prefix);
}
Spec spec = new Spec(annotationAttributes);
List<String> missingProperties = new ArrayList<String>();
List<String> nonMatchingProperties = new ArrayList<String>();
for (String name : names) {
String key = (relaxedNames ? name : prefix + name);
if (resolver.containsProperty(key)) {
if (!isMatch(resolver.getProperty(key), havingValue)) {
nonMatchingProperties.add(name);
}
}
else {
if (!matchIfMissing) {
missingProperties.add(name);
}
}
}
if (missingProperties.isEmpty() && nonMatchingProperties.isEmpty()) {
return ConditionOutcome.match();
}
StringBuilder message = new StringBuilder("@ConditionalOnProperty ");
spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
if (!missingProperties.isEmpty()) {
message.append("missing required properties ")
.append(expandNames(prefix, missingProperties)).append(" ");
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.didNotFind("property", "properties")
.items(Style.QUOTE, missingProperties));
}
if (!nonMatchingProperties.isEmpty()) {
String expected = StringUtils.hasLength(havingValue) ? havingValue : "!false";
message.append("expected '").append(expected).append("' for properties ")
.append(expandNames(prefix, nonMatchingProperties));
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.found("different value in property",
"different value in properties")
.items(Style.QUOTE, nonMatchingProperties));
}
return ConditionOutcome.noMatch(message.toString());
return ConditionOutcome.match(ConditionMessage
.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}
private String[] getNames(Map<String, Object> annotationAttributes) {
String[] value = (String[]) annotationAttributes.get("value");
String[] name = (String[]) annotationAttributes.get("name");
Assert.state(value.length > 0 || name.length > 0,
"The name or value attribute of @ConditionalOnProperty must be specified");
Assert.state(value.length == 0 || name.length == 0,
"The name and value attributes of @ConditionalOnProperty are exclusive");
return (value.length > 0 ? value : name);
}
private static class Spec {
private boolean isMatch(String value, String requiredValue) {
if (StringUtils.hasLength(requiredValue)) {
return requiredValue.equalsIgnoreCase(value);
private final String prefix;
private final String havingValue;
private final String[] names;
private final boolean relaxedNames;
private final boolean matchIfMissing;
Spec(AnnotationAttributes annotationAttributes) {
String prefix = annotationAttributes.getString("prefix").trim();
if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
prefix = prefix + ".";
}
this.prefix = prefix;
this.havingValue = annotationAttributes.getString("havingValue");
this.names = getNames(annotationAttributes);
this.relaxedNames = annotationAttributes.getBoolean("relaxedNames");
this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
}
return !"false".equalsIgnoreCase(value);
}
private String expandNames(String prefix, List<String> names) {
StringBuilder expanded = new StringBuilder();
for (String name : names) {
expanded.append(expanded.length() == 0 ? "" : ", ");
expanded.append(prefix);
expanded.append(name);
private String[] getNames(Map<String, Object> annotationAttributes) {
String[] value = (String[]) annotationAttributes.get("value");
String[] name = (String[]) annotationAttributes.get("name");
Assert.state(value.length > 0 || name.length > 0,
"The name or value attribute of @ConditionalOnProperty must be specified");
Assert.state(value.length == 0 || name.length == 0,
"The name and value attributes of @ConditionalOnProperty are exclusive");
return (value.length > 0 ? value : name);
}
private void collectProperties(PropertyResolver resolver, List<String> missing,
List<String> nonMatching) {
if (this.relaxedNames) {
resolver = new RelaxedPropertyResolver(resolver, this.prefix);
}
for (String name : this.names) {
String key = (this.relaxedNames ? name : this.prefix + name);
if (resolver.containsProperty(key)) {
if (!isMatch(resolver.getProperty(key), this.havingValue)) {
nonMatching.add(name);
}
}
else {
if (!this.matchIfMissing) {
missing.add(name);
}
}
}
}
return expanded.toString();
}
private String getCompositeMessage(List<ConditionOutcome> noMatchOutcomes) {
StringBuilder message = new StringBuilder();
for (ConditionOutcome noMatchOutcome : noMatchOutcomes) {
if (message.length() > 0) {
message.append(". ");
private boolean isMatch(String value, String requiredValue) {
if (StringUtils.hasLength(requiredValue)) {
return requiredValue.equalsIgnoreCase(value);
}
message.append(noMatchOutcome.getMessage().trim());
return !"false".equalsIgnoreCase(value);
}
return message.toString();
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("(");
result.append(this.prefix);
if (this.names.length == 1) {
result.append(this.names[0]);
}
else {
result.append("[");
result.append(StringUtils.arrayToCommaDelimitedString(this.names));
result.append("]");
}
if (StringUtils.hasLength(this.havingValue)) {
result.append("=").append(this.havingValue);
}
result.append(")");
return result.toString();
}
}
}
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
......@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.condition;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.io.DefaultResourceLoader;
......@@ -42,23 +43,28 @@ class OnResourceCondition extends SpringBootCondition {
AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true);
if (attributes != null) {
ResourceLoader loader = context.getResourceLoader() == null
? this.defaultResourceLoader : context.getResourceLoader();
List<String> locations = new ArrayList<String>();
collectValues(locations, attributes.get("resources"));
Assert.isTrue(!locations.isEmpty(),
"@ConditionalOnResource annotations must specify at least one resource location");
for (String location : locations) {
if (!loader
.getResource(
context.getEnvironment().resolvePlaceholders(location))
.exists()) {
return ConditionOutcome.noMatch("resource not found: " + location);
}
ResourceLoader loader = context.getResourceLoader() == null
? this.defaultResourceLoader : context.getResourceLoader();
List<String> locations = new ArrayList<String>();
collectValues(locations, attributes.get("resources"));
Assert.isTrue(!locations.isEmpty(),
"@ConditionalOnResource annotations must specify at "
+ "least one resource location");
List<String> missing = new ArrayList<String>();
for (String location : locations) {
String resouce = context.getEnvironment().resolvePlaceholders(location);
if (!loader.getResource(resouce).exists()) {
missing.add(location);
}
}
return ConditionOutcome.match();
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnResource.class)
.didNotFind("resource", "resources").items(Style.QUOTE, missing));
}
return ConditionOutcome
.match(ConditionMessage.forCondition(ConditionalOnResource.class)
.found("location", "locations").items(locations));
}
private void collectValues(List<String> names, List<Object> values) {
......
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2016 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.
......@@ -43,45 +43,40 @@ class OnWebApplicationCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
boolean webApplicationRequired = metadata
boolean required = metadata
.isAnnotated(ConditionalOnWebApplication.class.getName());
ConditionOutcome webApplication = isWebApplication(context, metadata);
if (webApplicationRequired && !webApplication.isMatch()) {
return ConditionOutcome.noMatch(webApplication.getMessage());
ConditionOutcome outcome = isWebApplication(context, metadata, required);
if (required && !outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
if (!webApplicationRequired && webApplication.isMatch()) {
return ConditionOutcome.noMatch(webApplication.getMessage());
if (!required && outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
return ConditionOutcome.match(webApplication.getMessage());
return ConditionOutcome.match(outcome.getConditionMessage());
}
private ConditionOutcome isWebApplication(ConditionContext context,
AnnotatedTypeMetadata metadata) {
AnnotatedTypeMetadata metadata, boolean required) {
ConditionMessage.Builder message = ConditionMessage.forCondition(
ConditionalOnWebApplication.class, required ? "(required)" : "");
if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) {
return ConditionOutcome.noMatch("web application classes not found");
return ConditionOutcome
.noMatch(message.didNotFind("web application classes").atAll());
}
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match("found web application 'session' scope");
return ConditionOutcome.match(message.foundExactly("'session' scope"));
}
}
if (context.getEnvironment() instanceof StandardServletEnvironment) {
return ConditionOutcome
.match("found web application StandardServletEnvironment");
.match(message.foundExactly("StandardServletEnvironment"));
}
if (context.getResourceLoader() instanceof WebApplicationContext) {
return ConditionOutcome.match("found web application WebApplicationContext");
return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
}
return ConditionOutcome.noMatch("not a web application");
return ConditionOutcome.noMatch(message.because("not a web application"));
}
}
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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,12 @@
package org.springframework.boot.autoconfigure.condition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.io.Resource;
......@@ -61,8 +67,8 @@ public abstract class ResourceCondition extends SpringBootCondition {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), this.prefix);
if (resolver.containsProperty(this.propertyName)) {
return ConditionOutcome.match("A '" + this.prefix + this.propertyName + "' "
+ "property is specified");
return ConditionOutcome.match(startConditionMessage()
.foundExactly("property " + this.prefix + this.propertyName));
}
return getResourceOutcome(context, metadata);
}
......@@ -75,15 +81,26 @@ public abstract class ResourceCondition extends SpringBootCondition {
*/
protected ConditionOutcome getResourceOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
List<String> found = new ArrayList<String>();
for (String location : this.resourceLocations) {
Resource resource = context.getResourceLoader().getResource(location);
if (resource != null && resource.exists()) {
return ConditionOutcome
.match("Found " + this.name + " config in " + resource);
found.add(location);
}
}
return ConditionOutcome
.noMatch("No specific " + this.name + " configuration found");
if (found.isEmpty()) {
ConditionMessage message = startConditionMessage()
.didNotFind("resource", "resources")
.items(Style.QUOTE, Arrays.asList(this.resourceLocations));
return ConditionOutcome.noMatch(message);
}
ConditionMessage message = startConditionMessage().found("resource", "resources")
.items(Style.QUOTE, found);
return ConditionOutcome.match(message);
}
protected final Builder startConditionMessage() {
return ConditionMessage.forCondition("ResourceCondition", "(" + this.name + ")");
}
}
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
......@@ -43,8 +43,8 @@ public abstract class HazelcastConfigResourceCondition extends ResourceCondition
protected ConditionOutcome getResourceOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
if (System.getProperty(CONFIG_SYSTEM_PROPERTY) != null) {
return ConditionOutcome
.match("System property '" + CONFIG_SYSTEM_PROPERTY + "' is set.");
return ConditionOutcome.match(startConditionMessage()
.because("System property '" + CONFIG_SYSTEM_PROPERTY + "' is set."));
}
return super.getResourceOutcome(context, metadata);
}
......
......@@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.Properties;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
......@@ -104,9 +105,13 @@ public class ProjectInfoAutoConfiguration {
location = "classpath:git.properties";
}
}
boolean match = loader.getResource(location).exists();
return new ConditionOutcome(match,
"Git info " + (match ? "found" : "not found") + " at " + location);
ConditionMessage.Builder message = ConditionMessage
.forCondition("GitResource");
if (loader.getResource(location).exists()) {
return ConditionOutcome.match(message.found("git info at").items(location));
}
return ConditionOutcome
.noMatch(message.didNotFind("git info at").items(location));
}
}
......
......@@ -31,6 +31,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
......@@ -156,10 +157,14 @@ public class DataSourceAutoConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("PooledDataSource");
if (getDataSourceClassLoader(context) != null) {
return ConditionOutcome.match("supported DataSource class found");
return ConditionOutcome
.match(message.foundExactly("supported DataSource"));
}
return ConditionOutcome.noMatch("missing supported DataSource");
return ConditionOutcome
.noMatch(message.didNotFind("supported DataSource").atAll());
}
/**
......@@ -187,15 +192,19 @@ public class DataSourceAutoConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("EmbeddedDataSource");
if (anyMatches(context, metadata, this.pooledCondition)) {
return ConditionOutcome.noMatch("supported DataSource class found");
return ConditionOutcome
.noMatch(message.foundExactly("supported pooled data source"));
}
EmbeddedDatabaseType type = EmbeddedDatabaseConnection
.get(context.getClassLoader()).getType();
if (type == null) {
return ConditionOutcome.noMatch("no embedded database detected");
return ConditionOutcome
.noMatch(message.didNotFind("embedded database").atAll());
}
return ConditionOutcome.match("embedded database " + type + " detected");
return ConditionOutcome.match(message.found("embedded database").items(type));
}
}
......@@ -214,16 +223,20 @@ public class DataSourceAutoConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("EmbeddedDataAvailble");
if (hasBean(context, DataSource.class)
|| hasBean(context, XADataSource.class)) {
return ConditionOutcome
.match("existing bean configured database detected");
.match(message.foundExactly("existing database bean"));
}
if (anyMatches(context, metadata, this.pooledCondition,
this.embeddedCondition)) {
return ConditionOutcome.match("existing auto database detected");
return ConditionOutcome
.match(message.foundExactly("existing auto-configured database"));
}
return ConditionOutcome.noMatch("no existing bean configured database");
return ConditionOutcome
.noMatch(message.didNotFind("any existing data source bean").atAll());
}
private boolean hasBean(ConditionContext context, Class<?> type) {
......
......@@ -116,8 +116,9 @@ public class ConditionEvaluationReportMessage {
.append(String.format("%n"));
for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
message.append(" - ");
if (StringUtils.hasLength(conditionAndOutcome.getOutcome().getMessage())) {
message.append(conditionAndOutcome.getOutcome().getMessage());
String outcomeMessage = conditionAndOutcome.getOutcome().getMessage();
if (StringUtils.hasLength(outcomeMessage)) {
message.append(outcomeMessage);
}
else {
message.append(conditionAndOutcome.getOutcome().isMatch() ? "matched"
......
......@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.orm.jpa;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
......@@ -28,6 +29,8 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
......@@ -199,13 +202,18 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("HibernateEntityManager");
for (String className : CLASS_NAMES) {
if (ClassUtils.isPresent(className, context.getClassLoader())) {
return ConditionOutcome.match("found HibernateEntityManager class");
return ConditionOutcome
.match(message.found("class").items(Style.QUOTE, className));
}
}
return ConditionOutcome.noMatch("did not find HibernateEntityManager class");
return ConditionOutcome.noMatch(message.didNotFind("class", "classes")
.items(Style.QUOTE, Arrays.asList(CLASS_NAMES)));
}
}
}
/*
* Copyright 2012-2016 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.security.oauth2.client;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Condition that checks for {@link EnableOAuth2Sso} on a
* {@link WebSecurityConfigurerAdapter}.
*
* @author Dave Syer
*/
class EnableOAuth2SsoCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String[] enablers = context.getBeanFactory()
.getBeanNamesForAnnotation(EnableOAuth2Sso.class);
ConditionMessage.Builder message = ConditionMessage
.forCondition("@EnableOAuth2Sso Condition");
for (String name : enablers) {
if (context.getBeanFactory().isTypeMatch(name,
WebSecurityConfigurerAdapter.class)) {
return ConditionOutcome.match(message
.found("@EnableOAuth2Sso annotation on WebSecurityConfigurerAdapter")
.items(name));
}
}
return ConditionOutcome.noMatch(message.didNotFind(
"@EnableOAuth2Sso annotation " + "on any WebSecurityConfigurerAdapter")
.atAll());
}
}
......@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.security.oauth2.client;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
......@@ -161,8 +162,14 @@ public class OAuth2RestOperationsConfiguration {
PropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "security.oauth2.client.");
String clientId = resolver.getProperty("client-id");
return new ConditionOutcome(StringUtils.hasLength(clientId),
"Non empty security.oauth2.client.client-id");
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth Client ID");
if (StringUtils.hasLength(clientId)) {
return ConditionOutcome.match(message
.foundExactly("security.oauth2.client.client-id property"));
}
return ConditionOutcome.match(message
.didNotFind("security.oauth2.client.client-id property").atAll());
}
}
......
......@@ -24,16 +24,11 @@ import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2SsoCustomConfiguration.WebSecurityEnhancerCondition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
......@@ -49,7 +44,7 @@ import org.springframework.util.ReflectionUtils;
* @author Dave Syer
*/
@Configuration
@Conditional(WebSecurityEnhancerCondition.class)
@Conditional(EnableOAuth2SsoCondition.class)
public class OAuth2SsoCustomConfiguration
implements ImportAware, BeanPostProcessor, ApplicationContextAware {
......@@ -111,24 +106,4 @@ public class OAuth2SsoCustomConfiguration
}
protected static class WebSecurityEnhancerCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String[] enablers = context.getBeanFactory()
.getBeanNamesForAnnotation(EnableOAuth2Sso.class);
for (String name : enablers) {
if (context.getBeanFactory().isTypeMatch(name,
WebSecurityConfigurerAdapter.class)) {
return ConditionOutcome.match(
"found @EnableOAuth2Sso on a WebSecurityConfigurerAdapter");
}
}
return ConditionOutcome.noMatch(
"found no @EnableOAuth2Sso on a WebSecurityConfigurerAdapter");
}
}
}
......@@ -17,7 +17,6 @@
package org.springframework.boot.autoconfigure.security.oauth2.client;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2SsoDefaultConfiguration.NeedsWebSecurityCondition;
import org.springframework.context.ApplicationContext;
......@@ -75,22 +74,12 @@ public class OAuth2SsoDefaultConfiguration extends WebSecurityConfigurerAdapter
return SecurityProperties.ACCESS_OVERRIDE_ORDER;
}
protected static class NeedsWebSecurityCondition extends SpringBootCondition {
protected static class NeedsWebSecurityCondition extends EnableOAuth2SsoCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String[] enablers = context.getBeanFactory()
.getBeanNamesForAnnotation(EnableOAuth2Sso.class);
for (String name : enablers) {
if (context.getBeanFactory().isTypeMatch(name,
WebSecurityConfigurerAdapter.class)) {
return ConditionOutcome.noMatch(
"found @EnableOAuth2Sso on a WebSecurityConfigurerAdapter");
}
}
return ConditionOutcome
.match("found no @EnableOAuth2Sso on a WebSecurityConfigurerAdapter");
return ConditionOutcome.inverse(super.getMatchOutcome(context, metadata));
}
}
......
......@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.security.oauth2.resource;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
......@@ -112,28 +113,32 @@ public class OAuth2ResourceServerConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth ResourceServer Condition");
Environment environment = context.getEnvironment();
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment,
"security.oauth2.resource.");
if (hasOAuthClientId(environment)) {
return ConditionOutcome.match("found client id");
return ConditionOutcome.match(message.foundExactly("client-id property"));
}
if (!resolver.getSubProperties("jwt").isEmpty()) {
return ConditionOutcome.match("found JWT resource configuration");
return ConditionOutcome
.match(message.foundExactly("JWT resource configuration"));
}
if (StringUtils.hasText(resolver.getProperty("user-info-uri"))) {
return ConditionOutcome
.match("found UserInfo " + "URI resource configuration");
.match(message.foundExactly("user-info-url property"));
}
if (ClassUtils.isPresent(AUTHORIZATION_ANNOTATION, null)) {
if (AuthorizationServerEndpointsConfigurationBeanCondition
.matches(context)) {
return ConditionOutcome.match(
"found authorization " + "server endpoints configuration");
return ConditionOutcome
.match(message.found("class").items(AUTHORIZATION_ANNOTATION));
}
}
return ConditionOutcome.noMatch("found neither client id nor "
+ "JWT resource nor authorization server");
return ConditionOutcome.noMatch(
message.didNotFind("client id, JWT resource or authorization server")
.atAll());
}
private boolean hasOAuthClientId(Environment environment) {
......
......@@ -25,6 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
......@@ -292,6 +293,8 @@ public class ResourceServerTokenServicesConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth TokenInfo Condition");
Environment environment = context.getEnvironment();
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment,
"security.oauth2.resource.");
......@@ -305,13 +308,14 @@ public class ResourceServerTokenServicesConfiguration {
String tokenInfoUri = resolver.getProperty("token-info-uri");
String userInfoUri = resolver.getProperty("user-info-uri");
if (!StringUtils.hasLength(userInfoUri)) {
return ConditionOutcome.match("No user info provided");
return ConditionOutcome
.match(message.didNotFind("user-info-uri property").atAll());
}
if (StringUtils.hasLength(tokenInfoUri) && preferTokenInfo) {
return ConditionOutcome.match(
"Token info endpoint " + "is preferred and user info provided");
return ConditionOutcome
.match(message.foundExactly("preferred token-info-uri property"));
}
return ConditionOutcome.noMatch("Token info endpoint is not provided");
return ConditionOutcome.noMatch(message.didNotFind("token info").atAll());
}
}
......@@ -321,14 +325,18 @@ public class ResourceServerTokenServicesConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth JWT Condition");
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "security.oauth2.resource.jwt.");
String keyValue = resolver.getProperty("key-value");
String keyUri = resolver.getProperty("key-uri");
if (StringUtils.hasText(keyValue) || StringUtils.hasText(keyUri)) {
return ConditionOutcome.match("public key is provided");
return ConditionOutcome
.match(message.foundExactly("provided public key"));
}
return ConditionOutcome.noMatch("public key is not provided");
return ConditionOutcome
.noMatch(message.didNotFind("provided public key").atAll());
}
}
......
......@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.session;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.bind.RelaxedPropertyResolver;
......@@ -38,22 +39,27 @@ class SessionCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Session Condition");
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "spring.session.");
StoreType sessionStoreType = SessionStoreMappings
.getType(((AnnotationMetadata) metadata).getClassName());
if (!resolver.containsProperty("store-type")) {
if (sessionStoreType == StoreType.REDIS && redisPresent) {
return ConditionOutcome
.match("Session store type default to redis (deprecated)");
return ConditionOutcome.match(
message.foundExactly("default store type of redis (deprecated)"));
}
return ConditionOutcome.noMatch("Session store type not set");
return ConditionOutcome.noMatch(
message.didNotFind("spring.session.store-type property").atAll());
}
String value = resolver.getProperty("store-type").replace("-", "_").toUpperCase();
if (value.equals(sessionStoreType.name())) {
return ConditionOutcome.match("Session store type " + sessionStoreType);
return ConditionOutcome.match(message
.found("spring.session.store-type property").items(sessionStoreType));
}
return ConditionOutcome.noMatch("Session store type " + value);
return ConditionOutcome
.noMatch(message.found("spring.session.store-type property").items(value));
}
}
......@@ -27,6 +27,8 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
......@@ -153,23 +155,28 @@ public class DispatcherServletAutoConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Default DispatcherServlet");
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
List<String> dispatchServletBeans = Arrays.asList(beanFactory
.getBeanNamesForType(DispatcherServlet.class, false, false));
if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome.noMatch("found DispatcherServlet named "
+ DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
return ConditionOutcome.noMatch(message.found("dispatcher servlet bean")
.items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome.noMatch("found non-DispatcherServlet named "
+ DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
return ConditionOutcome
.noMatch(message.found("non dispatcher servlet bean")
.items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
if (dispatchServletBeans.isEmpty()) {
return ConditionOutcome.match("no DispatcherServlet found");
return ConditionOutcome
.match(message.didNotFind("dispatcher servlet beans").atAll());
}
return ConditionOutcome
.match("one or more DispatcherServlets found and none is named "
+ DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
return ConditionOutcome.match(message
.found("dipatcher servlet bean", "dispatcher servlet beans")
.items(Style.QUOTE, dispatchServletBeans)
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
}
......@@ -197,39 +204,48 @@ public class DispatcherServletAutoConfiguration {
.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
if (containsDispatcherBean
&& !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome.noMatch("found non-DispatcherServlet named "
+ DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
return ConditionOutcome
.noMatch(startMessage().found("non dispatcher servlet")
.items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
return ConditionOutcome.match();
}
private ConditionOutcome checkServletRegistration(
ConfigurableListableBeanFactory beanFactory) {
ConditionMessage.Builder message = startMessage();
List<String> registrations = Arrays.asList(beanFactory
.getBeanNamesForType(ServletRegistrationBean.class, false, false));
boolean containsDispatcherRegistrationBean = beanFactory
.containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
if (registrations.isEmpty()) {
if (containsDispatcherRegistrationBean) {
return ConditionOutcome.noMatch("found no ServletRegistrationBean "
+ "but a non-ServletRegistrationBean named "
+ DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return ConditionOutcome
.noMatch(message.found("non servlet registration bean").items(
DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
return ConditionOutcome.match("no ServletRegistrationBean found");
return ConditionOutcome
.match(message.didNotFind("servlet registration bean").atAll());
}
if (registrations
.contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
return ConditionOutcome.noMatch("found ServletRegistrationBean named "
+ DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return ConditionOutcome.noMatch(message.found("servlet registration bean")
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
if (containsDispatcherRegistrationBean) {
return ConditionOutcome.noMatch("found non-ServletRegistrationBean named "
+ DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return ConditionOutcome
.noMatch(message.found("non servlet registration bean").items(
DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
return ConditionOutcome
.match("one or more ServletRegistrationBeans is found and none is named "
+ DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return ConditionOutcome.match(message.found("servlet registration beans")
.items(Style.QUOTE, registrations).append("and none is named "
+ DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
private ConditionMessage.Builder startMessage() {
return ConditionMessage.forCondition("DispatcherServlet Registration");
}
}
}
......@@ -32,6 +32,7 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
......@@ -166,6 +167,8 @@ public class ErrorMvcAutoConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("ErrorTemplate Misssing");
TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(
context.getClassLoader());
TemplateAvailabilityProvider provider = providers.getProvider("error",
......@@ -173,9 +176,10 @@ public class ErrorMvcAutoConfiguration {
context.getResourceLoader());
if (provider != null) {
return ConditionOutcome
.noMatch("Template from " + provider + " found for error view");
.noMatch(message.foundExactly("template from " + provider));
}
return ConditionOutcome.match("No error template view detected");
return ConditionOutcome
.match(message.didNotFind("error template view").atAll());
}
}
......
......@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.web;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.bind.PropertySourcesPropertyValues;
......@@ -31,6 +32,7 @@ import org.springframework.util.ClassUtils;
* enabled.
*
* @author Stephane Nicoll
* @see ConditionalOnEnabledResourceChain
*/
class OnEnabledResourceChainCondition extends SpringBootCondition {
......@@ -45,15 +47,21 @@ class OnEnabledResourceChainCondition extends SpringBootCondition {
RelaxedDataBinder binder = new RelaxedDataBinder(properties, "spring.resources");
binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources()));
Boolean match = properties.getChain().getEnabled();
ConditionMessage.Builder message = ConditionMessage
.forCondition(ConditionalOnEnabledResourceChain.class);
if (match == null) {
boolean webJarsLocatorPresent = ClassUtils.isPresent(WEBJAR_ASSERT_LOCATOR,
getClass().getClassLoader());
return new ConditionOutcome(webJarsLocatorPresent,
"Webjars locator (" + WEBJAR_ASSERT_LOCATOR + ") is "
+ (webJarsLocatorPresent ? "present" : "absent"));
if (ClassUtils.isPresent(WEBJAR_ASSERT_LOCATOR,
getClass().getClassLoader())) {
return ConditionOutcome
.match(message.found("class").items(WEBJAR_ASSERT_LOCATOR));
}
return ConditionOutcome
.noMatch(message.didNotFind("class").items(WEBJAR_ASSERT_LOCATOR));
}
return new ConditionOutcome(match,
"Resource chain is " + (match ? "enabled" : "disabled"));
if (match) {
return ConditionOutcome.match(message.because("enabled"));
}
return ConditionOutcome.noMatch(message.because("disabled"));
}
}
......@@ -216,9 +216,11 @@ public class ConditionEvaluationReportTests {
for (ConditionAndOutcome outcome : outcomes) {
messages.add(outcome.getOutcome().getMessage());
}
assertThat(messages).areAtLeastOne(Matched.by(
containsString("@ConditionalOnClass classes found: javax.servlet.Servlet,"
+ "org.springframework.web.multipart.support.StandardServletMultipartResolver")));
assertThat(messages).areAtLeastOne(
Matched.by(containsString("@ConditionalOnClass found required classes "
+ "'javax.servlet.Servlet', 'org.springframework.web.multipart."
+ "support.StandardServletMultipartResolver', "
+ "'javax.servlet.MultipartConfigElement'")));
context.close();
}
......
/*
* Copyright 2012-2016 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.List;
import org.junit.Test;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConditionMessage}.
*
* @author Phillip Webb
*/
public class ConditionMessageTests {
@Test
public void isEmptyWhenEmptyShouldReturnTrue() throws Exception {
ConditionMessage message = ConditionMessage.empty();
assertThat(message.isEmpty()).isTrue();
}
@Test
public void isEmptyWhenNotEmptyShouldReturnFalse() throws Exception {
ConditionMessage message = ConditionMessage.of("Test");
assertThat(message.isEmpty()).isFalse();
}
@Test
public void toStringWhenHasMessageShouldReturnMessage() throws Exception {
ConditionMessage message = ConditionMessage.empty();
assertThat(message.toString()).isEqualTo("");
}
@Test
public void toStringWhenEmptyShouldReturnEmptyString() throws Exception {
ConditionMessage message = ConditionMessage.of("Test");
assertThat(message.toString()).isEqualTo("Test");
}
@Test
public void appendWhenHasExistingMessageShouldAddSpace() throws Exception {
ConditionMessage message = ConditionMessage.of("a").append("b");
assertThat(message.toString()).isEqualTo("a b");
}
@Test
public void appendWhenAppendingNullShouldDoNothing() throws Exception {
ConditionMessage message = ConditionMessage.of("a").append(null);
assertThat(message.toString()).isEqualTo("a");
}
@Test
public void appendWhenNoMessageShouldNotAddSpace() throws Exception {
ConditionMessage message = ConditionMessage.empty().append("b");
assertThat(message.toString()).isEqualTo("b");
}
@Test
public void andConditionWhenUsingClassShouldIncludeCondition() throws Exception {
ConditionMessage message = ConditionMessage.empty().andCondition(Test.class)
.because("OK");
assertThat(message.toString()).isEqualTo("@Test OK");
}
@Test
public void andConditionWhenUsingStringShouldIncludeCondition() throws Exception {
ConditionMessage message = ConditionMessage.empty().andCondition("@Test")
.because("OK");
assertThat(message.toString()).isEqualTo("@Test OK");
}
@Test
public void andConditionWhenIncludingDetailsShouldIncludeCondition()
throws Exception {
ConditionMessage message = ConditionMessage.empty()
.andCondition(Test.class, "(a=b)").because("OK");
assertThat(message.toString()).isEqualTo("@Test (a=b) OK");
}
@Test
public void ofCollectionShouldCombine() throws Exception {
List<ConditionMessage> messages = new ArrayList<ConditionMessage>();
messages.add(ConditionMessage.of("a"));
messages.add(ConditionMessage.of("b"));
ConditionMessage message = ConditionMessage.of(messages);
assertThat(message.toString()).isEqualTo("a; b");
}
@Test
public void ofCollectionWhenNullShouldReturnEmpty() throws Exception {
ConditionMessage message = ConditionMessage.of((List<ConditionMessage>) null);
assertThat(message.isEmpty()).isTrue();
}
@Test
public void forConditionShouldIncludeCondition() throws Exception {
ConditionMessage message = ConditionMessage.forCondition("@Test").because("OK");
assertThat(message.toString()).isEqualTo("@Test OK");
}
@Test
public void forConditionWhenClassShouldIncludeCondition() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class, "(a=b)")
.because("OK");
assertThat(message.toString()).isEqualTo("@Test (a=b) OK");
}
@Test
public void foundExactlyShouldConstructMessage() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.foundExactly("abc");
assertThat(message.toString()).isEqualTo("@Test found abc");
}
@Test
public void foundWhenSingleElementShouldUsingSingular() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.found("bean", "beans").items("a");
assertThat(message.toString()).isEqualTo("@Test found bean a");
}
@Test
public void foundNoneAtAllShouldConstructMessage() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.found("no beans").atAll();
assertThat(message.toString()).isEqualTo("@Test found no beans");
}
@Test
public void foundWhenMultipleElementsShouldUsePlural() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.found("bean", "beans").items("a", "b", "c");
assertThat(message.toString()).isEqualTo("@Test found beans a, b, c");
}
@Test
public void foundWhenQuoteStyleShouldQuote() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.found("bean", "beans").items(Style.QUOTE, "a", "b", "c");
assertThat(message.toString()).isEqualTo("@Test found beans 'a', 'b', 'c'");
}
@Test
public void didNotFindWhenSingleElementShouldUsingSingular() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.didNotFind("class", "classes").items("a");
assertThat(message.toString()).isEqualTo("@Test did not find class a");
}
@Test
public void didNotFindWhenMultipleElementsShouldUsePlural() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.didNotFind("class", "classes").items("a", "b", "c");
assertThat(message.toString()).isEqualTo("@Test did not find classes a, b, c");
}
@Test
public void resultedInShouldConstructMessage() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.resultedIn("Green");
assertThat(message.toString()).isEqualTo("@Test resulted in Green");
}
@Test
public void notAvailableShouldConstructMessage() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.notAvailable("JMX");
assertThat(message.toString()).isEqualTo("@Test JMX is not available");
}
@Test
public void availableShouldConstructMessage() throws Exception {
ConditionMessage message = ConditionMessage.forCondition(Test.class)
.available("JMX");
assertThat(message.toString()).isEqualTo("@Test JMX is available");
}
}
......@@ -70,37 +70,45 @@ public class ConditionalOnClassTests {
@Configuration
@ConditionalOnClass(ConditionalOnClassTests.class)
protected static class BasicConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration
@ConditionalOnClass(name = "FOO")
protected static class MissingConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration
protected static class FooConfiguration {
@Bean
public String foo() {
return "foo";
}
}
@Configuration
@ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml")
protected static class XmlConfiguration {
}
@Configuration
@Import(BasicConfiguration.class)
@ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml")
protected static class CombinedXmlConfiguration {
}
}
......@@ -81,7 +81,7 @@ public class ConditionalOnJavaTests {
ConditionOutcome outcome = this.condition.getMatchOutcome(Range.EQUAL_OR_NEWER,
JavaVersion.SEVEN, JavaVersion.SIX);
assertThat(outcome.getMessage())
.isEqualTo("Required JVM version " + "1.6 or newer found 1.7");
.isEqualTo("@ConditionalOnJava (1.6 or newer) found 1.7");
}
@Test
......@@ -89,7 +89,7 @@ public class ConditionalOnJavaTests {
ConditionOutcome outcome = this.condition.getMatchOutcome(Range.OLDER_THAN,
JavaVersion.SEVEN, JavaVersion.SIX);
assertThat(outcome.getMessage())
.isEqualTo("Required JVM version " + "older than 1.6 found 1.7");
.isEqualTo("@ConditionalOnJava (older than 1.6) found 1.7");
}
@Test
......
......@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
......@@ -126,16 +127,18 @@ public class DevToolsDataSourceAutoConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("DevTools DataSource Condition");
String[] dataSourceBeanNames = context.getBeanFactory()
.getBeanNamesForType(DataSource.class);
if (dataSourceBeanNames.length != 1) {
return ConditionOutcome
.noMatch("A single DataSource bean was not found in the context");
.noMatch(message.didNotFind("a single DataSource bean").atAll());
}
if (context.getBeanFactory()
.getBeanNamesForType(DataSourceProperties.class).length != 1) {
return ConditionOutcome.noMatch(
"A single DataSourceProperties bean was not found in the context");
message.didNotFind("a single DataSourceProperties bean").atAll());
}
BeanDefinition dataSourceDefinition = context.getRegistry()
.getBeanDefinition(dataSourceBeanNames[0]);
......@@ -146,9 +149,11 @@ public class DevToolsDataSourceAutoConfiguration {
.getFactoryMethodMetadata().getDeclaringClassName()
.startsWith(DataSourceAutoConfiguration.class.getPackage()
.getName() + ".DataSourceConfiguration$")) {
return ConditionOutcome.match("Found auto-configured DataSource");
return ConditionOutcome
.match(message.foundExactly("auto-configured DataSource"));
}
return ConditionOutcome.noMatch("DataSource was not auto-configured");
return ConditionOutcome
.noMatch(message.didNotFind("an auto-configured DataSource").atAll());
}
}
......
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
......@@ -18,6 +18,7 @@ package org.springframework.boot.devtools.remote.client;
import javax.net.ServerSocketFactory;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.bind.RelaxedPropertyResolver;
......@@ -35,6 +36,8 @@ class LocalDebugPortAvailableCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Local Debug Port Condition");
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
context.getEnvironment(), "spring.devtools.remote.debug.");
Integer port = resolver.getProperty("local-port", Integer.class);
......@@ -42,9 +45,9 @@ class LocalDebugPortAvailableCondition extends SpringBootCondition {
port = RemoteDevToolsProperties.Debug.DEFAULT_LOCAL_PORT;
}
if (isPortAvailable(port)) {
return ConditionOutcome.match("Local debug port available");
return ConditionOutcome.match(message.foundExactly("local debug port"));
}
return ConditionOutcome.noMatch("Local debug port unavailable");
return ConditionOutcome.noMatch(message.didNotFind("local debug port").atAll());
}
private boolean isPortAvailable(int port) {
......
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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,7 @@
package org.springframework.boot.devtools.restart;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Condition;
......@@ -33,14 +34,16 @@ class OnInitializedRestarterCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Initializer Restarter Condition");
Restarter restarter = getRestarter();
if (restarter == null) {
return ConditionOutcome.noMatch("Restarter unavailable");
return ConditionOutcome.noMatch(message.because("unavailable"));
}
if (restarter.getInitialUrls() == null) {
return ConditionOutcome.noMatch("Restarter initialized without URLs");
return ConditionOutcome.noMatch(message.because("initialized without URLs"));
}
return ConditionOutcome.match("Restarter available and initialized");
return ConditionOutcome.match(message.because("available and initialized"));
}
private Restarter getRestarter() {
......
......@@ -47,7 +47,8 @@ public class LocalDebugPortAvailableConditionTests {
public void portAvailable() throws Exception {
ConditionOutcome outcome = getOutcome();
assertThat(outcome.isMatch()).isTrue();
assertThat(outcome.getMessage()).isEqualTo("Local debug port available");
assertThat(outcome.getMessage())
.isEqualTo("Local Debug Port Condition found local debug port");
}
@Test
......@@ -57,7 +58,8 @@ public class LocalDebugPortAvailableConditionTests {
ConditionOutcome outcome = getOutcome();
serverSocket.close();
assertThat(outcome.isMatch()).isFalse();
assertThat(outcome.getMessage()).isEqualTo("Local debug port unavailable");
assertThat(outcome.getMessage())
.isEqualTo("Local Debug Port Condition did not find local debug port");
}
private ConditionOutcome getOutcome() {
......
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