diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 18eff91001..6c95304f68 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -488,6 +488,10 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Initialize any placeholder property sources in the context environment initPropertySources(); + + // Validate that all properties marked as required are resolvable + // see ConfigurablePropertyResolver#setRequiredProperties + this.environment.validateRequiredProperties(); } /** diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index 823eacc440..eaf5f7592b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -216,6 +216,14 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { return this.propertyResolver.getRequiredProperty(key, targetType); } + public void setRequiredProperties(String... requiredProperties) { + this.propertyResolver.setRequiredProperties(requiredProperties); + } + + public void validateRequiredProperties() throws MissingRequiredPropertiesException { + this.propertyResolver.validateRequiredProperties(); + } + public String resolvePlaceholders(String text) { return this.propertyResolver.resolvePlaceholders(text); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java index ac36f02bbe..6b72de71ae 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java @@ -21,6 +21,9 @@ import static org.springframework.util.SystemPropertyUtils.PLACEHOLDER_PREFIX; import static org.springframework.util.SystemPropertyUtils.PLACEHOLDER_SUFFIX; import static org.springframework.util.SystemPropertyUtils.VALUE_SEPARATOR; +import java.util.LinkedHashSet; +import java.util.Set; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.convert.ConversionService; @@ -47,6 +50,8 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe private String placeholderSuffix = PLACEHOLDER_SUFFIX; private String valueSeparator = VALUE_SEPARATOR; + private final Set requiredProperties = new LinkedHashSet(); + public ConversionService getConversionService() { return this.conversionService; } @@ -65,6 +70,24 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe return value == null ? defaultValue : value; } + public void setRequiredProperties(String... requiredProperties) { + for (String key : requiredProperties) { + this.requiredProperties.add(key); + } + } + + public void validateRequiredProperties() { + MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); + for (String key : this.requiredProperties) { + if (this.getProperty(key) == null) { + ex.addMissingRequiredProperty(key); + } + } + if (!ex.getMissingRequiredProperties().isEmpty()) { + throw ex; + } + } + public String getRequiredProperty(String key) throws IllegalStateException { String value = getProperty(key); if (value == null) { diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java index e4574da219..e698f8d8f5 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java @@ -45,6 +45,23 @@ public interface ConfigurablePropertyResolver extends PropertyResolver { void setConversionService(ConversionService conversionService); void setPlaceholderPrefix(String placeholderPrefix); + void setPlaceholderSuffix(String placeholderSuffix); + void setValueSeparator(String valueSeparator); + + /** + * Specify which properties must be present, to be verified by + * {@link #validateRequiredProperties()}. + */ + void setRequiredProperties(String... requiredProperties); + + /** + * Validate that each of the properties specified by + * {@link #setRequiredProperties} is present and resolves to a + * non-{@code null} value. + * @throws MissingRequiredPropertiesException if any of the required + * properties are not resolvable. + */ + void validateRequiredProperties() throws MissingRequiredPropertiesException; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/MissingRequiredPropertiesException.java b/org.springframework.core/src/main/java/org/springframework/core/env/MissingRequiredPropertiesException.java new file mode 100644 index 0000000000..8c4d338e2c --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/env/MissingRequiredPropertiesException.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2011 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.core.env; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Exception thrown when required properties are not found. + * + * @author Chris Beams + * @since 3.1 + * @see ConfigurablePropertyResolver#setRequiredProperties(String...) + * @see ConfigurablePropertyResolver#validateRequiredProperties() + * @see org.springframework.context.support.AbstractApplicationContext#prepareRefresh() + */ +@SuppressWarnings("serial") +public class MissingRequiredPropertiesException extends IllegalStateException { + + private final Set missingRequiredProperties = new LinkedHashSet(); + + /** + * Return the set of properties marked as required but not present + * upon validation. + * @see ConfigurablePropertyResolver#setRequiredProperties(String...) + * @see ConfigurablePropertyResolver#validateRequiredProperties() + */ + public Set getMissingRequiredProperties() { + return missingRequiredProperties; + } + + void addMissingRequiredProperty(String key) { + missingRequiredProperties.add(key); + } + + @Override + public String getMessage() { + return String.format( + "The following properties were declared as required but could " + + "not be resolved: %s", this.getMissingRequiredProperties()); + } +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java index 53cd4936c4..eede760b95 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java @@ -135,7 +135,6 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver { return null; } - @SuppressWarnings("serial") static class ClassConversionException extends ConversionException { public ClassConversionException(Class actual, Class expected) { diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java index c9189008a1..4d02d12ef4 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java @@ -318,6 +318,40 @@ public class PropertySourcesPropertyResolverTests { resolver.getPropertyAsClass("some.class", SomeType.class); } + @Test + public void setRequiredProperties_andValidateRequiredProperties() { + // no properties have been marked as required -> validation should pass + propertyResolver.validateRequiredProperties(); + + // mark which properties are required + propertyResolver.setRequiredProperties("foo", "bar"); + + // neither foo nor bar properties are present -> validating should throw + try { + propertyResolver.validateRequiredProperties(); + fail("expected validation exception"); + } catch (MissingRequiredPropertiesException ex) { + assertThat(ex.getMessage(), equalTo( + "The following properties were declared as required " + + "but could not be resolved: [foo, bar]")); + } + + // add foo property -> validation should fail only on missing 'bar' property + testProperties.put("foo", "fooValue"); + try { + propertyResolver.validateRequiredProperties(); + fail("expected validation exception"); + } catch (MissingRequiredPropertiesException ex) { + assertThat(ex.getMessage(), equalTo( + "The following properties were declared as required " + + "but could not be resolved: [bar]")); + } + + // add bar property -> validation should pass, even with an empty string value + testProperties.put("bar", ""); + propertyResolver.validateRequiredProperties(); + } + static interface SomeType { } static class SpecificType implements SomeType { } } diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/core/env/EnvironmentIntegrationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/core/env/EnvironmentIntegrationTests.java index ca1a4dc202..30d0b2705a 100644 --- a/org.springframework.integration-tests/src/test/java/org/springframework/core/env/EnvironmentIntegrationTests.java +++ b/org.springframework.integration-tests/src/test/java/org/springframework/core/env/EnvironmentIntegrationTests.java @@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; import static org.springframework.context.ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME; import static org.springframework.core.env.EnvironmentIntegrationTests.Constants.DERIVED_DEV_BEAN_NAME; @@ -60,6 +61,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.jca.context.ResourceAdapterApplicationContext; import org.springframework.jca.support.SimpleBootstrapContext; import org.springframework.jca.work.SimpleTaskWorkManager; +import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockPropertySource; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; @@ -580,6 +582,32 @@ public class EnvironmentIntegrationTests { assertThat(ctx.containsBean(PROD_BEAN_NAME), is(true)); } + @Test + public void abstractApplicationContextValidatesRequiredPropertiesOnRefresh() { + { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.refresh(); + } + + { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.getEnvironment().setRequiredProperties("foo", "bar"); + try { + ctx.refresh(); + fail("expected missing property exception"); + } catch (MissingRequiredPropertiesException ex) { + } + } + + { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.getEnvironment().setRequiredProperties("foo"); + ctx.setEnvironment(new MockEnvironment().withProperty("foo", "fooValue")); + ctx.refresh(); // should succeed + } + + } + private DefaultListableBeanFactory newBeanFactoryWithEnvironmentAwareBean() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory();