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
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> requiredProperties = new LinkedHashSet<String>();
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class ClassConversionException extends ConversionException {
|
||||
public ClassConversionException(Class<?> actual, Class<?> expected) {
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue