org.springframework.test.context.support.TestPropertySourceUtils Maven / Gradle / Ivy
Show all versions of spring-test Show documentation
/*
* Copyright 2002-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
*
* https://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.test.context.support;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor;
/**
* Utility methods for working with {@link TestPropertySource @TestPropertySource}
* and adding test {@link PropertySource PropertySources} to the {@code Environment}.
*
* Primarily intended for use within the framework.
*
* @author Sam Brannen
* @since 4.1
* @see TestPropertySource
*/
public abstract class TestPropertySourceUtils {
/**
* The name of the {@link MapPropertySource} created from inlined properties.
* @since 4.1.5
* @see #addInlinedPropertiesToEnvironment
*/
public static final String INLINED_PROPERTIES_PROPERTY_SOURCE_NAME = "Inlined Test Properties";
private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class);
static MergedTestPropertySources buildMergedTestPropertySources(Class testClass) {
Class annotationType = TestPropertySource.class;
AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType);
if (descriptor == null) {
return new MergedTestPropertySources();
}
List attributesList = resolveTestPropertySourceAttributes(testClass);
String[] locations = mergeLocations(attributesList);
String[] properties = mergeProperties(attributesList);
return new MergedTestPropertySources(locations, properties);
}
private static List resolveTestPropertySourceAttributes(Class testClass) {
Assert.notNull(testClass, "Class must not be null");
List attributesList = new ArrayList<>();
Class annotationType = TestPropertySource.class;
AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType);
Assert.notNull(descriptor, String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
annotationType.getName(), testClass.getName()));
while (descriptor != null) {
TestPropertySource testPropertySource = descriptor.synthesizeAnnotation();
Class rootDeclaringClass = descriptor.getRootDeclaringClass();
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @TestPropertySource [%s] for declaring class [%s].",
testPropertySource, rootDeclaringClass.getName()));
}
TestPropertySourceAttributes attributes =
new TestPropertySourceAttributes(rootDeclaringClass, testPropertySource);
if (logger.isTraceEnabled()) {
logger.trace("Resolved TestPropertySource attributes: " + attributes);
}
attributesList.add(attributes);
descriptor = findAnnotationDescriptor(rootDeclaringClass.getSuperclass(), annotationType);
}
return attributesList;
}
private static String[] mergeLocations(List attributesList) {
List locations = new ArrayList<>();
for (TestPropertySourceAttributes attrs : attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Processing locations for TestPropertySource attributes %s", attrs));
}
String[] locationsArray = TestContextResourceUtils.convertToClasspathResourcePaths(
attrs.getDeclaringClass(), attrs.getLocations());
locations.addAll(0, Arrays.asList(locationsArray));
if (!attrs.isInheritLocations()) {
break;
}
}
return StringUtils.toStringArray(locations);
}
private static String[] mergeProperties(List attributesList) {
List properties = new ArrayList<>();
for (TestPropertySourceAttributes attrs : attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Processing inlined properties for TestPropertySource attributes %s", attrs));
}
String[] attrProps = attrs.getProperties();
if (attrProps != null) {
properties.addAll(0, Arrays.asList(attrProps));
}
if (!attrs.isInheritProperties()) {
break;
}
}
return StringUtils.toStringArray(properties);
}
/**
* Add the {@link Properties} files from the given resource {@code locations}
* to the {@link Environment} of the supplied {@code context}.
* This method simply delegates to
* {@link #addPropertiesFilesToEnvironment(ConfigurableEnvironment, ResourceLoader, String...)}.
* @param context the application context whose environment should be updated;
* never {@code null}
* @param locations the resource locations of {@code Properties} files to add
* to the environment; potentially empty but never {@code null}
* @throws IllegalStateException if an error occurs while processing a properties file
* @since 4.1.5
* @see ResourcePropertySource
* @see TestPropertySource#locations
* @see #addPropertiesFilesToEnvironment(ConfigurableEnvironment, ResourceLoader, String...)
*/
public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContext context, String... locations) {
Assert.notNull(context, "'context' must not be null");
Assert.notNull(locations, "'locations' must not be null");
addPropertiesFilesToEnvironment(context.getEnvironment(), context, locations);
}
/**
* Add the {@link Properties} files from the given resource {@code locations}
* to the supplied {@link ConfigurableEnvironment environment}.
*
Property placeholders in resource locations (i.e., ${...}
)
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}.
*
Each properties file will be converted to a {@link ResourcePropertySource}
* that will be added to the {@link PropertySources} of the environment with
* highest precedence.
* @param environment the environment to update; never {@code null}
* @param resourceLoader the {@code ResourceLoader} to use to load each resource;
* never {@code null}
* @param locations the resource locations of {@code Properties} files to add
* to the environment; potentially empty but never {@code null}
* @throws IllegalStateException if an error occurs while processing a properties file
* @since 4.3
* @see ResourcePropertySource
* @see TestPropertySource#locations
* @see #addPropertiesFilesToEnvironment(ConfigurableApplicationContext, String...)
*/
public static void addPropertiesFilesToEnvironment(ConfigurableEnvironment environment,
ResourceLoader resourceLoader, String... locations) {
Assert.notNull(environment, "'environment' must not be null");
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
Assert.notNull(locations, "'locations' must not be null");
try {
for (String location : locations) {
String resolvedLocation = environment.resolveRequiredPlaceholders(location);
Resource resource = resourceLoader.getResource(resolvedLocation);
environment.getPropertySources().addFirst(new ResourcePropertySource(resource));
}
}
catch (IOException ex) {
throw new IllegalStateException("Failed to add PropertySource to Environment", ex);
}
}
/**
* Add the given inlined properties to the {@link Environment} of the
* supplied {@code context}.
*
This method simply delegates to
* {@link #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])}.
* @param context the application context whose environment should be updated;
* never {@code null}
* @param inlinedProperties the inlined properties to add to the environment;
* potentially empty but never {@code null}
* @since 4.1.5
* @see TestPropertySource#properties
* @see #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])
*/
public static void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context, String... inlinedProperties) {
Assert.notNull(context, "'context' must not be null");
Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null");
addInlinedPropertiesToEnvironment(context.getEnvironment(), inlinedProperties);
}
/**
* Add the given inlined properties (in the form of key-value
* pairs) to the supplied {@link ConfigurableEnvironment environment}.
*
All key-value pairs will be added to the {@code Environment} as a
* single {@link MapPropertySource} with the highest precedence.
*
For details on the parsing of inlined properties, consult the
* Javadoc for {@link #convertInlinedPropertiesToMap}.
* @param environment the environment to update; never {@code null}
* @param inlinedProperties the inlined properties to add to the environment;
* potentially empty but never {@code null}
* @since 4.1.5
* @see MapPropertySource
* @see #INLINED_PROPERTIES_PROPERTY_SOURCE_NAME
* @see TestPropertySource#properties
* @see #convertInlinedPropertiesToMap
*/
public static void addInlinedPropertiesToEnvironment(ConfigurableEnvironment environment, String... inlinedProperties) {
Assert.notNull(environment, "'environment' must not be null");
Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null");
if (!ObjectUtils.isEmpty(inlinedProperties)) {
if (logger.isDebugEnabled()) {
logger.debug("Adding inlined properties to environment: " +
ObjectUtils.nullSafeToString(inlinedProperties));
}
MapPropertySource ps = (MapPropertySource)
environment.getPropertySources().get(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
if (ps == null) {
ps = new MapPropertySource(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME, new LinkedHashMap<>());
environment.getPropertySources().addFirst(ps);
}
ps.getSource().putAll(convertInlinedPropertiesToMap(inlinedProperties));
}
}
/**
* Convert the supplied inlined properties (in the form of key-value
* pairs) into a map keyed by property name, preserving the ordering of property names
* in the returned map.
*
Parsing of the key-value pairs is achieved by converting all pairs
* into virtual properties files in memory and delegating to
* {@link Properties#load(java.io.Reader)} to parse each virtual file.
*
For a full discussion of inlined properties, consult the Javadoc
* for {@link TestPropertySource#properties}.
* @param inlinedProperties the inlined properties to convert; potentially empty
* but never {@code null}
* @return a new, ordered map containing the converted properties
* @throws IllegalStateException if a given key-value pair cannot be parsed, or if
* a given inlined property contains multiple key-value pairs
* @since 4.1.5
* @see #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])
*/
public static Map convertInlinedPropertiesToMap(String... inlinedProperties) {
Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null");
Map map = new LinkedHashMap<>();
Properties props = new Properties();
for (String pair : inlinedProperties) {
if (!StringUtils.hasText(pair)) {
continue;
}
try {
props.load(new StringReader(pair));
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load test environment property from [" + pair + "]", ex);
}
Assert.state(props.size() == 1, () -> "Failed to load exactly one test environment property from [" + pair + "]");
for (String name : props.stringPropertyNames()) {
map.put(name, props.getProperty(name));
}
props.clear();
}
return map;
}
}