Support 'required properties' precondition
Users may now call #setRequiredProperties(String...) against the Environment (via its ConfigurablePropertyResolver interface) in order to indicate which properties must be present. Environment#validateRequiredProperties() is invoked by AbstractApplicationContext during the refresh() lifecycle to perform the actual check and a MissingRequiredPropertiesException is thrown if the precondition is not satisfied. Issue: SPR-8323
This commit is contained in:
parent
3622c6f340
commit
404f798048
|
|
@ -488,6 +488,10 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||||
|
|
||||||
// Initialize any placeholder property sources in the context environment
|
// Initialize any placeholder property sources in the context environment
|
||||||
initPropertySources();
|
initPropertySources();
|
||||||
|
|
||||||
|
// Validate that all properties marked as required are resolvable
|
||||||
|
// see ConfigurablePropertyResolver#setRequiredProperties
|
||||||
|
this.environment.validateRequiredProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,14 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||||
return this.propertyResolver.getRequiredProperty(key, targetType);
|
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) {
|
public String resolvePlaceholders(String text) {
|
||||||
return this.propertyResolver.resolvePlaceholders(text);
|
return this.propertyResolver.resolvePlaceholders(text);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.PLACEHOLDER_SUFFIX;
|
||||||
import static org.springframework.util.SystemPropertyUtils.VALUE_SEPARATOR;
|
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.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.core.convert.ConversionService;
|
import org.springframework.core.convert.ConversionService;
|
||||||
|
|
@ -47,6 +50,8 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||||
private String placeholderSuffix = PLACEHOLDER_SUFFIX;
|
private String placeholderSuffix = PLACEHOLDER_SUFFIX;
|
||||||
private String valueSeparator = VALUE_SEPARATOR;
|
private String valueSeparator = VALUE_SEPARATOR;
|
||||||
|
|
||||||
|
private final Set<String> requiredProperties = new LinkedHashSet<String>();
|
||||||
|
|
||||||
public ConversionService getConversionService() {
|
public ConversionService getConversionService() {
|
||||||
return this.conversionService;
|
return this.conversionService;
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +70,24 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||||
return value == null ? defaultValue : value;
|
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 {
|
public String getRequiredProperty(String key) throws IllegalStateException {
|
||||||
String value = getProperty(key);
|
String value = getProperty(key);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,23 @@ public interface ConfigurablePropertyResolver extends PropertyResolver {
|
||||||
void setConversionService(ConversionService conversionService);
|
void setConversionService(ConversionService conversionService);
|
||||||
|
|
||||||
void setPlaceholderPrefix(String placeholderPrefix);
|
void setPlaceholderPrefix(String placeholderPrefix);
|
||||||
|
|
||||||
void setPlaceholderSuffix(String placeholderSuffix);
|
void setPlaceholderSuffix(String placeholderSuffix);
|
||||||
|
|
||||||
void setValueSeparator(String valueSeparator);
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<String> missingRequiredProperties = new LinkedHashSet<String>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the set of properties marked as required but not present
|
||||||
|
* upon validation.
|
||||||
|
* @see ConfigurablePropertyResolver#setRequiredProperties(String...)
|
||||||
|
* @see ConfigurablePropertyResolver#validateRequiredProperties()
|
||||||
|
*/
|
||||||
|
public Set<String> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -135,7 +135,6 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
static class ClassConversionException extends ConversionException {
|
static class ClassConversionException extends ConversionException {
|
||||||
public ClassConversionException(Class<?> actual, Class<?> expected) {
|
public ClassConversionException(Class<?> actual, Class<?> expected) {
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,40 @@ public class PropertySourcesPropertyResolverTests {
|
||||||
resolver.getPropertyAsClass("some.class", SomeType.class);
|
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 interface SomeType { }
|
||||||
static class SpecificType implements SomeType { }
|
static class SpecificType implements SomeType { }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.lessThan;
|
import static org.hamcrest.Matchers.lessThan;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.junit.Assert.assertThat;
|
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.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
|
||||||
import static org.springframework.context.ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME;
|
import static org.springframework.context.ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME;
|
||||||
import static org.springframework.core.env.EnvironmentIntegrationTests.Constants.DERIVED_DEV_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.context.ResourceAdapterApplicationContext;
|
||||||
import org.springframework.jca.support.SimpleBootstrapContext;
|
import org.springframework.jca.support.SimpleBootstrapContext;
|
||||||
import org.springframework.jca.work.SimpleTaskWorkManager;
|
import org.springframework.jca.work.SimpleTaskWorkManager;
|
||||||
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
import org.springframework.mock.env.MockPropertySource;
|
import org.springframework.mock.env.MockPropertySource;
|
||||||
import org.springframework.mock.web.MockServletConfig;
|
import org.springframework.mock.web.MockServletConfig;
|
||||||
import org.springframework.mock.web.MockServletContext;
|
import org.springframework.mock.web.MockServletContext;
|
||||||
|
|
@ -580,6 +582,32 @@ public class EnvironmentIntegrationTests {
|
||||||
assertThat(ctx.containsBean(PROD_BEAN_NAME), is(true));
|
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() {
|
private DefaultListableBeanFactory newBeanFactoryWithEnvironmentAwareBean() {
|
||||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue