Add InvalidConfigurationPropertyValueException
This commit adds a new exception type that denotes the value of a configuration key is invalid, alongside a FailureAnalyzer that reports a human-readable report when such exception is thrown on startup. ResourceNotFoundException being a (useless) specialization of this new exception, its usage has been refactored to use the more general exception type. Closes gh-10794pull/11528/merge
parent
6daad1f562
commit
b98c7ed9f8
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012-2017 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.context.config;
|
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown when a {@link Resource} defined by a property is not found.
|
|
||||||
*
|
|
||||||
* @author Stephane Nicoll
|
|
||||||
* @since 1.5.0
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class ResourceNotFoundException extends RuntimeException {
|
|
||||||
|
|
||||||
private final String propertyName;
|
|
||||||
|
|
||||||
private final Resource resource;
|
|
||||||
|
|
||||||
public ResourceNotFoundException(String propertyName, Resource resource) {
|
|
||||||
super(String.format("%s defined by '%s' does not exist", resource, propertyName));
|
|
||||||
this.propertyName = propertyName;
|
|
||||||
this.resource = resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the name of the property that defines the resource.
|
|
||||||
* @return the property
|
|
||||||
*/
|
|
||||||
public String getPropertyName() {
|
|
||||||
return this.propertyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the {@link Resource}.
|
|
||||||
* @return the resource
|
|
||||||
*/
|
|
||||||
public Resource getResource() {
|
|
||||||
return this.resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2018 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.context.properties.source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a configuration property value is invalid.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class InvalidConfigurationPropertyValueException
|
||||||
|
extends RuntimeException {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final Object value;
|
||||||
|
|
||||||
|
private final String reason;
|
||||||
|
|
||||||
|
public InvalidConfigurationPropertyValueException(String name, Object value,
|
||||||
|
String reason) {
|
||||||
|
super(String.format("Property %s with value '%s' is invalid: %s", name,
|
||||||
|
value, reason));
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name of the property.
|
||||||
|
* @return the property name
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the invalid value, can be {@code null}.
|
||||||
|
* @return the invalid value
|
||||||
|
*/
|
||||||
|
public Object getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the reason why the value is invalid.
|
||||||
|
* @return the reason
|
||||||
|
*/
|
||||||
|
public String getReason() {
|
||||||
|
return this.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2018 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.diagnostics.analyzer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||||
|
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||||
|
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
|
||||||
|
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||||
|
import org.springframework.boot.diagnostics.FailureAnalyzer;
|
||||||
|
import org.springframework.boot.origin.OriginLookup;
|
||||||
|
import org.springframework.context.EnvironmentAware;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link FailureAnalyzer} that performs analysis of failures caused by a
|
||||||
|
* {@link InvalidConfigurationPropertyValueException}.
|
||||||
|
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class InvalidConfigurationPropertyValueFailureAnalyzer
|
||||||
|
extends AbstractFailureAnalyzer<InvalidConfigurationPropertyValueException>
|
||||||
|
implements EnvironmentAware {
|
||||||
|
|
||||||
|
private ConfigurableEnvironment environment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnvironment(Environment environment) {
|
||||||
|
this.environment = (ConfigurableEnvironment) environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FailureAnalysis analyze(Throwable rootFailure,
|
||||||
|
InvalidConfigurationPropertyValueException cause) {
|
||||||
|
List<PropertyValueDescriptor> descriptors = getPropertySourceDescriptors(cause.getName());
|
||||||
|
if (descriptors.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PropertyValueDescriptor main = descriptors.get(0);
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
message.append(String.format("The value '%s'", main.value));
|
||||||
|
if (main.origin != null) {
|
||||||
|
message.append(String.format(" from origin '%s'", main.origin));
|
||||||
|
}
|
||||||
|
message.append(String.format(" of configuration property '%s' was invalid. ",
|
||||||
|
cause.getName()));
|
||||||
|
if (StringUtils.hasText(cause.getReason())) {
|
||||||
|
message.append(String.format(
|
||||||
|
"Validation failed for the following reason:%n%n"));
|
||||||
|
message.append(cause.getReason());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message.append("No reason was provided.");
|
||||||
|
}
|
||||||
|
List<PropertyValueDescriptor> others = descriptors.subList(1, descriptors.size());
|
||||||
|
if (!others.isEmpty()) {
|
||||||
|
message.append(String.format(
|
||||||
|
"%n%nAdditionally, this property is also set in the following "
|
||||||
|
+ "property %s:%n%n",
|
||||||
|
others.size() > 1 ? "sources" : "source"));
|
||||||
|
for (PropertyValueDescriptor other : others) {
|
||||||
|
message.append(String.format("\t- %s: %s%n", other.propertySource,
|
||||||
|
other.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringBuilder action = new StringBuilder();
|
||||||
|
action.append("Review the value of the property");
|
||||||
|
if (cause.getReason() != null) {
|
||||||
|
action.append(" with the provided reason");
|
||||||
|
}
|
||||||
|
action.append(".");
|
||||||
|
return new FailureAnalysis(message.toString(), action.toString(), cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PropertyValueDescriptor> getPropertySourceDescriptors(
|
||||||
|
String propertyName) {
|
||||||
|
List<PropertyValueDescriptor> propertySources = new ArrayList<>();
|
||||||
|
getPropertySourcesAsMap()
|
||||||
|
.forEach((sourceName, source) -> {
|
||||||
|
if (source.containsProperty(propertyName)) {
|
||||||
|
propertySources.add(describeValueOf(propertyName, source));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return propertySources;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
|
||||||
|
Map<String, PropertySource<?>> map = new LinkedHashMap<>();
|
||||||
|
if (this.environment != null) {
|
||||||
|
for (PropertySource<?> source : this.environment.getPropertySources()) {
|
||||||
|
if (!ConfigurationPropertySources
|
||||||
|
.isAttachedConfigurationPropertySource(source)) {
|
||||||
|
map.put(source.getName(), source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PropertyValueDescriptor describeValueOf(String name,
|
||||||
|
PropertySource<?> source) {
|
||||||
|
Object value = source.getProperty(name);
|
||||||
|
String origin = (source instanceof OriginLookup)
|
||||||
|
? ((OriginLookup<Object>) source).getOrigin(name).toString() : null;
|
||||||
|
return new PropertyValueDescriptor(source.getName(), value, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PropertyValueDescriptor {
|
||||||
|
|
||||||
|
private final String propertySource;
|
||||||
|
|
||||||
|
private final Object value;
|
||||||
|
|
||||||
|
private final String origin;
|
||||||
|
|
||||||
|
PropertyValueDescriptor(String propertySource,
|
||||||
|
Object value, String origin) {
|
||||||
|
this.propertySource = propertySource;
|
||||||
|
this.value = value;
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2018 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.diagnostics.analyzer;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||||
|
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||||
|
import org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter;
|
||||||
|
import org.springframework.boot.origin.Origin;
|
||||||
|
import org.springframework.boot.origin.OriginLookup;
|
||||||
|
import org.springframework.core.env.EnumerablePropertySource;
|
||||||
|
import org.springframework.core.env.MapPropertySource;
|
||||||
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link InvalidConfigurationPropertyValueFailureAnalyzer}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class InvalidConfigurationPropertyValueFailureAnalyzerTests {
|
||||||
|
|
||||||
|
private final MockEnvironment environment = new MockEnvironment();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void analysisWithNullEnvironment() {
|
||||||
|
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
|
||||||
|
"test.property", "invalid", "this is not valid");
|
||||||
|
FailureAnalysis analysis = new InvalidConfigurationPropertyValueFailureAnalyzer()
|
||||||
|
.analyze(failure);
|
||||||
|
assertThat(analysis).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void analysisWithKnownProperty() {
|
||||||
|
MapPropertySource source = new MapPropertySource("test",
|
||||||
|
Collections.singletonMap("test.property", "invalid"));
|
||||||
|
this.environment.getPropertySources().addFirst(new OriginCapablePropertySource(source));
|
||||||
|
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
|
||||||
|
"test.property", "invalid", "this is not valid");
|
||||||
|
FailureAnalysis analysis = performAnalysis(failure);
|
||||||
|
assertCommonParts(failure, analysis);
|
||||||
|
assertThat(analysis.getDescription())
|
||||||
|
.contains("Validation failed for the following reason")
|
||||||
|
.contains("this is not valid")
|
||||||
|
.doesNotContain("Additionally, this property is also set");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void analysisWithKnownPropertyAndNoReason() {
|
||||||
|
MapPropertySource source = new MapPropertySource("test",
|
||||||
|
Collections.singletonMap("test.property", "invalid"));
|
||||||
|
this.environment.getPropertySources().addFirst(new OriginCapablePropertySource(source));
|
||||||
|
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
|
||||||
|
"test.property", "invalid", null);
|
||||||
|
FailureAnalysis analysis = performAnalysis(failure);
|
||||||
|
assertCommonParts(failure, analysis);
|
||||||
|
assertThat(analysis.getDescription())
|
||||||
|
.contains("No reason was provided.")
|
||||||
|
.doesNotContain("Additionally, this property is also set");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void analysisWithKnownPropertyAndOtherCandidates() {
|
||||||
|
MapPropertySource source = new MapPropertySource("test",
|
||||||
|
Collections.singletonMap("test.property", "invalid"));
|
||||||
|
MapPropertySource additional = new MapPropertySource("additional",
|
||||||
|
Collections.singletonMap("test.property", "valid"));
|
||||||
|
MapPropertySource another = new MapPropertySource("another",
|
||||||
|
Collections.singletonMap("test.property", "test"));
|
||||||
|
this.environment.getPropertySources().addFirst(new OriginCapablePropertySource(source));
|
||||||
|
this.environment.getPropertySources().addLast(additional);
|
||||||
|
this.environment.getPropertySources().addLast(another);
|
||||||
|
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
|
||||||
|
"test.property", "invalid", "this is not valid");
|
||||||
|
FailureAnalysis analysis = performAnalysis(failure);
|
||||||
|
assertCommonParts(failure, analysis);
|
||||||
|
assertThat(analysis.getDescription())
|
||||||
|
.contains("Additionally, this property is also set in the following " +
|
||||||
|
"property sources:")
|
||||||
|
.contains("additional: valid").contains("another: test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void analysisWithUnknownKey() {
|
||||||
|
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
|
||||||
|
"test.key.not.defined", "invalid", "this is not valid");
|
||||||
|
assertThat(performAnalysis(failure)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertCommonParts(InvalidConfigurationPropertyValueException failure,
|
||||||
|
FailureAnalysis analysis) {
|
||||||
|
assertThat(analysis.getDescription()).contains("test.property")
|
||||||
|
.contains("invalid").contains("TestOrigin test.property");
|
||||||
|
assertThat(analysis.getAction()
|
||||||
|
.contains("Review the value of the property with the provided reason."));
|
||||||
|
assertThat(analysis.getCause()).isSameAs(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private FailureAnalysis performAnalysis(
|
||||||
|
InvalidConfigurationPropertyValueException failure) {
|
||||||
|
InvalidConfigurationPropertyValueFailureAnalyzer analyzer = new InvalidConfigurationPropertyValueFailureAnalyzer();
|
||||||
|
analyzer.setEnvironment(this.environment);
|
||||||
|
FailureAnalysis analysis = analyzer.analyze(failure);
|
||||||
|
if (analysis != null) {
|
||||||
|
new LoggingFailureAnalysisReporter().report(analysis);
|
||||||
|
}
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OriginCapablePropertySource<T>
|
||||||
|
extends EnumerablePropertySource<T> implements OriginLookup<String> {
|
||||||
|
|
||||||
|
private final EnumerablePropertySource<T> propertySource;
|
||||||
|
|
||||||
|
OriginCapablePropertySource(EnumerablePropertySource<T> propertySource) {
|
||||||
|
super(propertySource.getName(), propertySource.getSource());
|
||||||
|
this.propertySource = propertySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getProperty(String name) {
|
||||||
|
return this.propertySource.getProperty(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getPropertyNames() {
|
||||||
|
return this.propertySource.getPropertyNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Origin getOrigin(String name) {
|
||||||
|
return new Origin() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TestOrigin " + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue