diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointMBeanExportAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointMBeanExportAutoConfiguration.java index 9d990a50dfc..01a0c045b1a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointMBeanExportAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointMBeanExportAutoConfiguration.java @@ -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) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java index dae4058e9eb..7580a0bdc38 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java @@ -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")); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java index b12cd3f8ded..eecfbda3784 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java @@ -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()); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfiguration.java index 4abe23a18d3..1e6e45ea88f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/JolokiaAutoConfiguration.java @@ -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, diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java index bc4a48f08ab..609562e412f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java @@ -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")); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledEndpointElementCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledEndpointElementCondition.java index 7a3dbc221ef..0084e11c861 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledEndpointElementCondition.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledEndpointElementCondition.java @@ -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 annotationType; - OnEnabledEndpointElementCondition(String prefix, Class annotationType) { + OnEnabledEndpointElementCondition(String prefix, + Class 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)); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java index 1f1d609c5b9..ffd903100a1 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/condition/OnEnabledEndpointCondition.java @@ -1,5 +1,5 @@ /* - * 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); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/MessageSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/MessageSourceAutoConfiguration.java index dddc7ee9528..65cd9a0a0b9 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/MessageSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/MessageSourceAutoConfiguration.java @@ -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) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheCondition.java index 4c8fbc66c21..aa35f9f07ba 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheCondition.java @@ -1,5 +1,5 @@ /* - * 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")); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java index ac54618c093..0882cf7f33c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java @@ -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 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")); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractNestedCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractNestedCondition.java index 4422b97548b..927b17b9e27 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractNestedCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractNestedCondition.java @@ -1,5 +1,5 @@ /* - * 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)); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AllNestedConditions.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AllNestedConditions.java index 9638ec1f2ac..d430540ab2f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AllNestedConditions.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AllNestedConditions.java @@ -1,5 +1,5 @@ /* - * 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 messages = new ArrayList(); + 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(); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AnyNestedCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AnyNestedCondition.java index 3656fe42a0c..eea34624248 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AnyNestedCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AnyNestedCondition.java @@ -1,5 +1,5 @@ /* - * 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 messages = new ArrayList(); + 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)); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java index 7d0d7d2a844..17cecda76a4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java @@ -1,5 +1,5 @@ /* - * 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 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); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionMessage.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionMessage.java new file mode 100644 index 00000000000..5b029f56d6c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionMessage.java @@ -0,0 +1,435 @@ +/* + * 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 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 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 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 result = new ArrayList(); + for (Object item : items) { + result.add(applyToItem(item)); + } + return result; + } + + protected abstract Object applyToItem(Object item); + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionOutcome.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionOutcome.java index 9d2b9cc6883..386c228de9d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionOutcome.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionOutcome.java @@ -1,5 +1,5 @@ /* - * 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()); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneNestedConditions.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneNestedConditions.java index 81ea014cc70..9bbe3f5f50d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneNestedConditions.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneNestedConditions.java @@ -1,5 +1,5 @@ /* - * 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 messages = new ArrayList(); + 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)); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index cb666716029..95680f74e6b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -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 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 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 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 getMatchingBeans(ConditionContext context, diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java index 916f2ceba53..1709b23268b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java @@ -1,5 +1,5 @@ /* - * 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 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 onMissingClasses = getAttributes(metadata, ConditionalOnMissingClass.class); if (onMissingClasses != null) { List 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 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); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnExpressionCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnExpressionCondition.java index f4b4d2cf9d2..8027b2cdeb8 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnExpressionCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnExpressionCondition.java @@ -1,5 +1,5 @@ /* - * 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; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJavaCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJavaCondition.java index fc65d7b8fb8..a37b05461a4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJavaCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJavaCondition.java @@ -1,5 +1,5 @@ /* - * 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); } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJndiCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJndiCondition.java index 954f7fe1daf..9d3243a17be 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJndiCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJndiCondition.java @@ -1,5 +1,5 @@ /* - * 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() { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java index f075b0f8407..ed77ccedd4c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java @@ -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 allAnnotationAttributes = annotationAttributesFromMultiValueMap( metadata.getAllAnnotationAttributes( ConditionalOnProperty.class.getName())); - List noMatchOutcomes = findNoMatchOutcomes( - allAnnotationAttributes, context.getEnvironment()); - if (noMatchOutcomes.isEmpty()) { - return ConditionOutcome.match(); + List noMatch = new ArrayList(); + List match = new ArrayList(); + 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 annotationAttributesFromMultiValueMap( @@ -82,104 +88,110 @@ class OnPropertyCondition extends SpringBootCondition { return annotationAttributes; } - private List findNoMatchOutcomes( - List allAnnotationAttributes, - PropertyResolver resolver) { - List noMatchOutcomes = new ArrayList( - 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 missingProperties = new ArrayList(); List nonMatchingProperties = new ArrayList(); - 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 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 names) { - StringBuilder expanded = new StringBuilder(); - for (String name : names) { - expanded.append(expanded.length() == 0 ? "" : ", "); - expanded.append(prefix); - expanded.append(name); + private String[] getNames(Map 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 missing, + List 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 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(); + } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnResourceCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnResourceCondition.java index 8ce4a7960d3..64fb241f3a5 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnResourceCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnResourceCondition.java @@ -1,5 +1,5 @@ /* - * 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 attributes = metadata .getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true); - if (attributes != null) { - ResourceLoader loader = context.getResourceLoader() == null - ? this.defaultResourceLoader : context.getResourceLoader(); - List locations = new ArrayList(); - 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 locations = new ArrayList(); + collectValues(locations, attributes.get("resources")); + Assert.isTrue(!locations.isEmpty(), + "@ConditionalOnResource annotations must specify at " + + "least one resource location"); + List missing = new ArrayList(); + 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 names, List values) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java index 7537a5f723c..e06f0fba239 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnWebApplicationCondition.java @@ -1,5 +1,5 @@ /* - * 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")); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ResourceCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ResourceCondition.java index 20e4fcab350..d98280872f4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ResourceCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ResourceCondition.java @@ -1,5 +1,5 @@ /* - * 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 found = new ArrayList(); 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 + ")"); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigResourceCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigResourceCondition.java index 19b34355f4a..0dd8cc6961d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigResourceCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigResourceCondition.java @@ -1,5 +1,5 @@ /* - * 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); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.java index 33049887554..6e5d0ddc23c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.java @@ -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)); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index e186020e34c..b39aa5405c7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -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) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportMessage.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportMessage.java index 0ac510fdd3c..15f2585d2a4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportMessage.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportMessage.java @@ -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" diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java index 6abaee1842e..4d1a38c69a7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java @@ -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))); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/EnableOAuth2SsoCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/EnableOAuth2SsoCondition.java new file mode 100644 index 00000000000..2ccc4feaeb1 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/EnableOAuth2SsoCondition.java @@ -0,0 +1,54 @@ +/* + * 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()); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java index 0333e84e9d0..03899630fa1 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java @@ -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()); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoCustomConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoCustomConfiguration.java index 1de85326cee..7b5bb69c0fe 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoCustomConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoCustomConfiguration.java @@ -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"); - } - - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoDefaultConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoDefaultConfiguration.java index a6d91302116..6943d70f71c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoDefaultConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoDefaultConfiguration.java @@ -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)); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerConfiguration.java index 948f00a382d..dabdaa33064 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerConfiguration.java @@ -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) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java index 35f47ba844d..d0313a64884 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java @@ -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()); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java index 84797a817ab..6ecefb5d702 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java @@ -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)); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java index f4d63b1783b..1f245309412 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java @@ -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 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 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"); + } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java index 41f7db057e5..f5e08181d94 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java @@ -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()); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/OnEnabledResourceChainCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/OnEnabledResourceChainCondition.java index 7a05d1b117f..264043dceac 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/OnEnabledResourceChainCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/OnEnabledResourceChainCondition.java @@ -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")); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java index 4935271dbdb..5639723bd23 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java @@ -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(); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionMessageTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionMessageTests.java new file mode 100644 index 00000000000..2d1e3160b61 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionMessageTests.java @@ -0,0 +1,197 @@ +/* + * 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 messages = new ArrayList(); + 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) 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"); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnClassTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnClassTests.java index f8ada70d2a4..16227d18b2f 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnClassTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnClassTests.java @@ -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 { + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJavaTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJavaTests.java index 3736d400107..6458a32a7f9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJavaTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJavaTests.java @@ -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 diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java index 6bd4293d3d1..2c867b50135 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java @@ -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()); } } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/LocalDebugPortAvailableCondition.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/LocalDebugPortAvailableCondition.java index 1b336c8d98b..5b8d6606f1a 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/LocalDebugPortAvailableCondition.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/LocalDebugPortAvailableCondition.java @@ -1,5 +1,5 @@ /* - * 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) { diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/OnInitializedRestarterCondition.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/OnInitializedRestarterCondition.java index c09aa5fe296..2ef5097c6e6 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/OnInitializedRestarterCondition.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/OnInitializedRestarterCondition.java @@ -1,5 +1,5 @@ /* - * 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() { diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/remote/client/LocalDebugPortAvailableConditionTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/remote/client/LocalDebugPortAvailableConditionTests.java index 25c2f0790e2..e912a7989da 100644 --- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/remote/client/LocalDebugPortAvailableConditionTests.java +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/remote/client/LocalDebugPortAvailableConditionTests.java @@ -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() {