diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyValue.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyValue.java new file mode 100644 index 00000000000..8971b7e80d0 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyValue.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional} that only matches when a property + * has a given value. + * + *

If {@link #defaultMatch()} is {@code true} then the + * condition also matches if the property + * is not present at all. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +@Conditional(OnPropertyValueCondition.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ConditionalOnPropertyValue { + + /** + * A prefix that should be applied to the property. + * Defaults to no prefix. The prefix automatically + * ends with a dot, it does not need to be added. + */ + String prefix() default ""; + + /** + * The property to check. If a prefix has been defined, it + * is applied to compute the full key of the property. For + * instance if the prefix is {@code app.config} and this + * property is {@code my-value}, the fully key would be + * {@code app.config.my-value} + *

Use the dashed notation to specify the property, that + * is all lower case with a "-" to separate words (e.g. + * {@code my-long-property}) + */ + String property(); + + /** + * If relaxed names should be checked. Defaults to {@code true}. + */ + boolean relaxedName() default true; + + /** + * The string representation of the expected value for the property. + */ + String value(); + + /** + * Specify if the condition should match if the property is not set. + * Defaults to {@code false} + *

This means that the specified {@link #value()} is actually + * the default one (i.e. the one that you expect if the property + * is not set). + */ + boolean defaultMatch() default false; + +} 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 a729c490846..382599523cf 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 @@ -18,11 +18,10 @@ package org.springframework.boot.autoconfigure.condition; import java.util.ArrayList; import java.util.List; +import java.util.Map; -import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.env.PropertyResolver; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.StringUtils; @@ -31,6 +30,7 @@ import org.springframework.util.StringUtils; * * @author Maciej Walkowiak * @author Phillip Webb + * @author Stephane Nicoll * @see ConditionalOnProperty * @since 1.1.0 */ @@ -39,30 +39,20 @@ class OnPropertyCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = metadata + .getAnnotationAttributes(ConditionalOnProperty.class.getName()); - String prefix = ((String) metadata.getAnnotationAttributes( - ConditionalOnProperty.class.getName()).get("prefix")).trim(); - if (!"".equals(prefix) && !prefix.endsWith(".")) { - prefix = prefix + "."; - } - String[] names = (String[]) metadata.getAnnotationAttributes( - ConditionalOnProperty.class.getName()).get("value"); - Boolean relaxedNames = (Boolean) metadata.getAnnotationAttributes( - ConditionalOnProperty.class.getName()).get("relaxedNames"); + String prefix = ((String) attributes.get("prefix")).trim(); + Boolean relaxedNames = (Boolean) attributes.get("relaxedNames"); + String[] names = (String[]) attributes.get("value"); + PropertyHelper helper = new PropertyHelper(context.getEnvironment(), prefix, relaxedNames); List missingProperties = new ArrayList(); - - PropertyResolver resolver = context.getEnvironment(); - if (relaxedNames) { - resolver = new RelaxedPropertyResolver(resolver, prefix); - prefix = ""; - } - for (String name : names) { - name = prefix + name; - if (!resolver.containsProperty(name) - || "false".equalsIgnoreCase(resolver.getProperty(name))) { - missingProperties.add(name); + String propertyKey = helper.createPropertyKey(name); + if (!helper.getPropertyResolver().containsProperty(propertyKey) + || "false".equalsIgnoreCase(helper.getPropertyResolver().getProperty(propertyKey))) { + missingProperties.add(propertyKey); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyValueCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyValueCondition.java new file mode 100644 index 00000000000..47d3224524e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyValueCondition.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import java.util.Map; + +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link org.springframework.context.annotation.Condition Condition} that + * checks if a property has a given value. Can also be configured so that + * the value to check is the default, hence the absence of property matches + * as well. + * + * @author Stephane Nicoll + * @since 1.2.0 + * @see ConditionalOnPropertyValue + */ +public class OnPropertyValueCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = metadata + .getAnnotationAttributes(ConditionalOnPropertyValue.class.getName()); + String prefix = ((String) attributes.get("prefix")).trim(); + String property = (String) attributes.get("property"); + Boolean relaxedName = (Boolean) attributes.get("relaxedName"); + String value = (String) attributes.get("value"); + Boolean defaultMatch = (Boolean) attributes.get("defaultMatch"); + + PropertyHelper helper = new PropertyHelper(context.getEnvironment(), prefix, relaxedName); + String propertyKey = helper.createPropertyKey(property); + String actualValue = helper.getPropertyResolver().getProperty(propertyKey); + + if (actualValue == null && defaultMatch) { + return ConditionOutcome.match("@ConditionalOnProperty no property '" + property + + "' is set, assuming default value '" + value + "'"); + } + if (!value.equalsIgnoreCase(actualValue)) { + return ConditionOutcome.noMatch("@ConditionalOnProperty wrong value for property '" + + property + "': expected '" + value + "' but got '" + actualValue + "'"); + } + return ConditionOutcome.match(); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/PropertyHelper.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/PropertyHelper.java new file mode 100644 index 00000000000..ab44da3a63d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/PropertyHelper.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.core.env.PropertyResolver; +import org.springframework.util.StringUtils; + +/** + * Helper for properties-related operations. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +class PropertyHelper { + + private final PropertyResolver propertyResolver; + + private final String prefix; + + private final boolean relaxedNames; + + /** + * Create a new instance with the base {@link PropertyResolver} to use. If + * a prefix is set and does not end with a dot, it is added. + * + * @param propertyResolver the base property resolver + * @param prefix the prefix to lookup keys, if any + * @param relaxedNames if relaxed names should be checked + */ + PropertyHelper(PropertyResolver propertyResolver, String prefix, boolean relaxedNames) { + this.prefix = cleanPrefix(prefix); + this.relaxedNames = relaxedNames; + if (relaxedNames) { + this.propertyResolver = new RelaxedPropertyResolver(propertyResolver, this.prefix); + } + else { + this.propertyResolver = propertyResolver; + } + } + + /** + * Return the {@link PropertyResolver} to use. + */ + public PropertyResolver getPropertyResolver() { + return propertyResolver; + } + + /** + * Create the full property key for the specified property. Applies the + * configured prefix, if necessary. + */ + public String createPropertyKey(String property) { + return (relaxedNames ? property : prefix + property); + } + + private static String cleanPrefix(String prefix) { + return (StringUtils.hasText(prefix) && !prefix.endsWith(".") ? prefix + "." : prefix); + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyValueTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyValueTests.java new file mode 100644 index 00000000000..da40bd39585 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyValueTests.java @@ -0,0 +1,196 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Tests for {@link ConditionalOnPropertyValue} + * + * @author Stephane Nicoll + */ +public class ConditionalOnPropertyValueTests { + + private AnnotationConfigApplicationContext context; + + @After + public void tearDown() { + if (this.context != null) { + this.context.close(); + } + } + + @Test // Enabled by default + public void enabledIfNotConfiguredOtherwise() { + load(EnabledIfNotConfiguredOtherwiseConfig.class); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void enabledIfNotConfiguredOtherwiseWithConfig() { + load(EnabledIfNotConfiguredOtherwiseConfig.class, "simple.myProperty:false"); + assertFalse(this.context.containsBean("foo")); + } + + @Test + public void enabledIfNotConfiguredOtherwiseWithConfigDifferentCase() { + load(EnabledIfNotConfiguredOtherwiseConfig.class, "simple.my-property:FALSE"); + assertFalse(this.context.containsBean("foo")); + } + + @Test // Disabled by default + public void disableIfNotConfiguredOtherwise() { + load(DisabledIfNotConfiguredOtherwiseConfig.class); + assertFalse(this.context.containsBean("foo")); + } + + @Test + public void disableIfNotConfiguredOtherwiseWithConfig() { + load(DisabledIfNotConfiguredOtherwiseConfig.class, "simple.myProperty:true"); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void disableIfNotConfiguredOtherwiseWithConfigDifferentCase() { + load(DisabledIfNotConfiguredOtherwiseConfig.class, "simple.myproperty:TrUe"); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void simpleValueIsSet() { + load(SimpleValueConfig.class, "simple.myProperty:bar"); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void caseInsensitive() { + load(SimpleValueConfig.class, "simple.myProperty:BaR"); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void defaultValueIsSet() { + load(DefaultValueConfig.class, "simple.myProperty:bar"); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void defaultValueIsNotSet() { + load(DefaultValueConfig.class); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void defaultValueIsSetDifferentValue() { + load(DefaultValueConfig.class, "simple.myProperty:another"); + assertFalse(this.context.containsBean("foo")); + } + + @Test + public void prefix() { + load(PrefixValueConfig.class, "simple.myProperty:bar"); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void relaxedEnabledByDefault() { + load(PrefixValueConfig.class, "simple.myProperty:bar"); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void strictNameMatch() { + load(StrictNameConfig.class, "simple.my-property:bar"); + assertTrue(this.context.containsBean("foo")); + } + + @Test + public void strictNameNoMatch() { + load(StrictNameConfig.class, "simple.myProperty:bar"); + assertFalse(this.context.containsBean("foo")); + } + + private void load(Class config, String... environment) { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, environment); + this.context.register(config); + this.context.refresh(); + } + + @Configuration // ${simple.myProperty:true} + @ConditionalOnPropertyValue(prefix = "simple", property = "my-property", value = "true", defaultMatch = true) + static class EnabledIfNotConfiguredOtherwiseConfig { + @Bean + public String foo() { + return "foo"; + } + } + + @Configuration // ${simple.myProperty:false} + @ConditionalOnPropertyValue(prefix = "simple", property = "my-property", value = "true", defaultMatch = false) + static class DisabledIfNotConfiguredOtherwiseConfig { + @Bean + public String foo() { + return "foo"; + } + } + + @Configuration + @ConditionalOnPropertyValue(prefix = "simple", property = "my-property", value = "bar") + static class SimpleValueConfig { + @Bean + public String foo() { + return "foo"; + } + } + + @Configuration + @ConditionalOnPropertyValue(property = "simple.myProperty", value = "bar", defaultMatch = true) + static class DefaultValueConfig { + @Bean + public String foo() { + return "foo"; + } + } + + @Configuration + @ConditionalOnPropertyValue(prefix = "simple", property = "my-property", value = "bar") + static class PrefixValueConfig { + @Bean + public String foo() { + return "foo"; + } + } + + @Configuration + @ConditionalOnPropertyValue(prefix = "simple", property = "my-property", value = "bar", relaxedName = false) + static class StrictNameConfig { + @Bean + public String foo() { + return "foo"; + } + } + +}