M1 cut of environment, profiles and property work (SPR-7508)

Decomposed Environment interface into PropertySources, PropertyResolver
objects

    Environment interface and implementations are still present, but
    simpler.

    PropertySources container aggregates PropertySource objects;
    PropertyResolver provides search, conversion, placeholder
    replacement. Single implementation for now is
    PropertySourcesPlaceholderResolver

Renamed EnvironmentAwarePropertyPlaceholderConfigurer to
PropertySourcesPlaceholderConfigurer

    <context:property-placeholder/> now registers PSPC by default, else
    PPC if systemPropertiesMode* settings are involved

Refined configuration and behavior of default profiles

    See Environment interface Javadoc for details

Added Portlet implementations of relevant interfaces:

    * DefaultPortletEnvironment
    * PortletConfigPropertySource, PortletContextPropertySource
    * Integrated each appropriately throughout Portlet app contexts

Added protected 'createEnvironment()' method to AbstractApplicationContext

    Subclasses can override at will to supply a custom Environment
    implementation.  In practice throughout the framework, this is how
    Web- and Portlet-related ApplicationContexts override use of the
    DefaultEnvironment and swap in DefaultWebEnvironment or
    DefaultPortletEnvironment as appropriate.

Introduced "stub-and-replace" behavior for Servlet- and Portlet-based
PropertySource implementations

    Allows for early registration and ordering of the stub, then
    replacement with actual backing object at refresh() time.

    Added AbstractApplicationContext.initPropertySources() method to
    support stub-and-replace behavior. Called from within existing
    prepareRefresh() method so as to avoid impact with
    ApplicationContext implementations that copy and modify AAC's
    refresh() method (e.g.: Spring DM).

    Added methods to WebApplicationContextUtils and
    PortletApplicationContextUtils to support stub-and-replace behavior

Added comprehensive Javadoc for all new or modified types and members

Added XSD documentation for all new or modified elements and attributes

    Including nested <beans>, <beans profile="..."/>, and changes for
    certain attributes type from xsd:IDREF to xsd:string

Improved fix for detecting non-file based Resources in
PropertiesLoaderSupport (SPR-7547, SPR-7552)

    Technically unrelated to environment work, but grouped in with
    this changeset for convenience.

Deprecated (removed) context:property-placeholder
'system-properties-mode' attribute from spring-context-3.1.xsd

    Functionality is preserved for those using schemas up to and including
    spring-context-3.0.  For 3.1, system-properties-mode is no longer
    supported as it conflicts with the idea of managing a set of property
    sources within the context's Environment object. See Javadoc in
    PropertyPlaceholderConfigurer, AbstractPropertyPlaceholderConfigurer
    and PropertySourcesPlaceholderConfigurer for details.

Introduced CollectionUtils.toArray(Enumeration<E>, A[])

Work items remaining for 3.1 M2:

    Consider repackaging PropertySource* types; eliminate internal use
    of SystemPropertyUtils and deprecate

    Further work on composition of Environment interface; consider
    repurposing existing PlaceholderResolver interface to obviate need
    for resolve[Required]Placeholder() methods currently in Environment.

    Ensure configurability of placeholder prefix, suffix, and value
    separator when working against an AbstractPropertyResolver

    Add JNDI-based Environment / PropertySource implementatinos

    Consider support for @Profile at the @Bean level

    Provide consistent logging for the entire property resolution
    lifecycle; consider issuing all such messages against a dedicated
    logger with a single category.

    Add reference documentation to cover the featureset.
This commit is contained in:
Chris Beams
2011-01-03 09:04:34 +00:00
parent b130a36af7
commit b3ff9be78f
111 changed files with 3439 additions and 1715 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -16,39 +16,40 @@
package org.springframework.core.env;
import static java.lang.String.format;
import static org.springframework.util.StringUtils.commaDelimitedListToSet;
import static org.springframework.util.StringUtils.trimAllWhitespace;
import java.security.AccessControlException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import static java.lang.String.*;
import static org.springframework.util.StringUtils.*;
import static org.springframework.util.SystemPropertyUtils.*;
/**
* TODO SPR-7508: document
* Abstract base class for {@link Environment} implementations.
*
* @author Chris Beams
* @since 3.1
* @see DefaultEnvironment
*/
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
/**
* Name of property to set to specify active profiles: {@value}. May be comma delimited.
* @see ConfigurableEnvironment#setActiveProfiles
*/
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profile.active";
/**
* Name of property to set to specify default profiles: {@value}. May be comma delimited.
* @see ConfigurableEnvironment#setDefaultProfiles
*/
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profile.default";
protected final Log logger = LogFactory.getLog(getClass());
@@ -56,138 +57,22 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
private Set<String> activeProfiles = new LinkedHashSet<String>();
private Set<String> defaultProfiles = new LinkedHashSet<String>();
private LinkedList<PropertySource<?>> propertySources = new LinkedList<PropertySource<?>>();
private ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
private final PropertyPlaceholderHelper nonStrictHelper =
new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, true);
private final PropertyPlaceholderHelper strictHelper =
new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, false);
private MutablePropertySources propertySources = new MutablePropertySources();
private ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
public ConversionService getConversionService() {
return this.conversionService;
public String[] getActiveProfiles() {
return this.doGetActiveProfiles().toArray(new String[]{});
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void addPropertySource(PropertySource<?> propertySource) {
propertySources.addFirst(propertySource);
}
public void addPropertySource(String name, Properties properties) {
addPropertySource(new PropertiesPropertySource(name, properties));
}
public void addPropertySource(String name, Map<String, String> propertiesMap) {
addPropertySource(new MapPropertySource(name, propertiesMap));
}
public LinkedList<PropertySource<?>> getPropertySources() {
return propertySources;
}
public boolean containsProperty(String key) {
for (PropertySource<?> propertySource : propertySources) {
if (propertySource.containsProperty(key)) {
return true;
}
}
return false;
}
public String getProperty(String key) {
if (logger.isTraceEnabled()) {
logger.trace(format("getProperty(\"%s\") (implicit targetType [String])", key));
}
return getProperty(key, String.class);
}
public String getRequiredProperty(String key) {
String value = getProperty(key);
if (value == null) {
throw new IllegalArgumentException(format("required key [%s] not found", key));
}
return value;
}
public <T> T getProperty(String key, Class<T> targetValueType) {
boolean debugEnabled = logger.isDebugEnabled();
if (logger.isTraceEnabled()) {
logger.trace(format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName()));
}
for (PropertySource<?> propertySource : propertySources) {
if (debugEnabled) {
logger.debug(format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
if (propertySource.containsProperty(key)) {
Object value = propertySource.getProperty(key);
Class<?> valueType = value == null ? null : value.getClass();
if (debugEnabled) {
logger.debug(
format("Found key '%s' in [%s] with type [%s] and value '%s'",
key, propertySource.getName(),
valueType == null ? "" : valueType.getSimpleName(), value));
}
if (value == null) {
return null;
}
if (!conversionService.canConvert(valueType, targetValueType)) {
throw new IllegalArgumentException(
format("Cannot convert value [%s] from source type [%s] to target type [%s]",
value, valueType.getSimpleName(), targetValueType.getSimpleName()));
}
return conversionService.convert(value, targetValueType);
}
}
if (debugEnabled) {
logger.debug(format("Could not find key '%s' in any property source. Returning [null]", key));
}
return null;
}
public <T> T getRequiredProperty(String key, Class<T> valueType) {
T value = getProperty(key, valueType);
if (value == null) {
throw new IllegalArgumentException(format("required key [%s] not found", key));
}
return value;
}
public int getPropertyCount() {
return asProperties().size();
}
public Properties asProperties() {
// TODO SPR-7508: refactor, simplify. only handles map-based propertysources right now.
Properties mergedProps = new Properties();
for (int i = propertySources.size() -1; i >= 0; i--) {
PropertySource<?> propertySource = propertySources.get(i);
Object object = propertySource.getSource();
if (object instanceof Map) {
for (Entry<?, ?> entry : ((Map<?, ?>)object).entrySet()) {
mergedProps.put(entry.getKey(), entry.getValue());
}
} else {
throw new IllegalArgumentException("unknown PropertySource source type: " + object.getClass().getName());
}
}
return mergedProps;
}
public Set<String> getActiveProfiles() {
protected Set<String> doGetActiveProfiles() {
if (this.activeProfiles.isEmpty()) {
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
String profiles = this.propertyResolver.getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
this.activeProfiles = commaDelimitedListToSet(trimAllWhitespace(profiles));
}
}
return Collections.unmodifiableSet(activeProfiles);
return this.activeProfiles;
}
public void setActiveProfiles(String... profiles) {
@@ -195,14 +80,18 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
this.activeProfiles.addAll(Arrays.asList(profiles));
}
public Set<String> getDefaultProfiles() {
public String[] getDefaultProfiles() {
return this.doGetDefaultProfiles().toArray(new String[]{});
}
protected Set<String> doGetDefaultProfiles() {
if (this.defaultProfiles.isEmpty()) {
String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
String profiles = this.propertyResolver.getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
this.defaultProfiles = commaDelimitedListToSet(profiles);
this.defaultProfiles = commaDelimitedListToSet(trimAllWhitespace(profiles));
}
}
return Collections.unmodifiableSet(this.defaultProfiles);
return this.defaultProfiles;
}
public void setDefaultProfiles(String... profiles) {
@@ -210,6 +99,30 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
this.defaultProfiles.addAll(Arrays.asList(profiles));
}
public boolean acceptsProfiles(String... profiles) {
Assert.notEmpty(profiles, "Must specify at least one profile");
boolean activeProfileFound = false;
Set<String> activeProfiles = this.doGetActiveProfiles();
Set<String> defaultProfiles = this.doGetDefaultProfiles();
for (String profile : profiles) {
Assert.hasText(profile, "profile must not be empty");
if (activeProfiles.contains(profile)
|| (activeProfiles.isEmpty() && defaultProfiles.contains(profile))) {
activeProfileFound = true;
break;
}
}
return activeProfileFound;
}
public MutablePropertySources getPropertySources() {
return this.propertySources;
}
public ConfigurablePropertyResolver getPropertyResolver() {
return this.propertyResolver;
}
public Map<String, String> getSystemEnvironment() {
Map<String,String> systemEnvironment;
try {
@@ -235,17 +148,6 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
return systemEnvironment;
}
/**
* TODO SPR-7508: document
*
* Returns a string, string map even though the underlying system properties
* are a properties object that can technically contain non-string keys and values.
* Thus, the unchecked conversions and raw map type being used. In practice, it will
* always be 'safe' to interact with the properties map as if it contains only strings,
* because Properties copes with this in its getProperty method. We never access the
* properties object via its Hashtable.get() method, so any non-string keys/values
* get effectively ignored.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<String, String> getSystemProperties() {
Map systemProperties;
@@ -272,40 +174,10 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
return systemProperties;
}
public String resolvePlaceholders(String text) {
return doResolvePlaceholders(text, nonStrictHelper);
}
public String resolveRequiredPlaceholders(String text) {
return doResolvePlaceholders(text, strictHelper);
}
public boolean acceptsProfiles(String[] specifiedProfiles) {
boolean activeProfileFound = false;
Set<String> activeProfiles = this.getActiveProfiles();
Set<String> defaultProfiles = this.getDefaultProfiles();
for (String profile : specifiedProfiles) {
if (activeProfiles.contains(profile)
|| (activeProfiles.isEmpty() && defaultProfiles.contains(profile))) {
activeProfileFound = true;
break;
}
}
return activeProfileFound;
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PlaceholderResolver() {
public String resolvePlaceholder(String placeholderName) {
return AbstractEnvironment.this.getProperty(placeholderName);
}
});
}
@Override
public String toString() {
return String.format("%s [activeProfiles=%s, defaultProfiles=%s, propertySources=%s]",
getClass().getSimpleName(), activeProfiles, defaultProfiles, propertySources);
return format("%s [activeProfiles=%s, defaultProfiles=%s, propertySources=%s]",
getClass().getSimpleName(), this.activeProfiles, this.defaultProfiles, this.propertySources);
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2010 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 static java.lang.String.format;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
/**
* Abstract base class for resolving properties against any underlying source.
*
* @author Chris Beams
* @since 3.1
*/
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
protected final Log logger = LogFactory.getLog(getClass());
protected ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
private final PropertyPlaceholderHelper nonStrictHelper =
new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, true);
private final PropertyPlaceholderHelper strictHelper =
new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, false);
public ConversionService getConversionService() {
return this.conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public String getRequiredProperty(String key) throws IllegalStateException {
String value = getProperty(key);
if (value == null) {
throw new IllegalStateException(format("required key [%s] not found", key));
}
return value;
}
public <T> T getRequiredProperty(String key, Class<T> valueType) throws IllegalStateException {
T value = getProperty(key, valueType);
if (value == null) {
throw new IllegalStateException(format("required key [%s] not found", key));
}
return value;
}
public int getPropertyCount() {
return asProperties().size();
}
public String resolvePlaceholders(String text) {
return doResolvePlaceholders(text, this.nonStrictHelper);
}
public String resolveRequiredPlaceholders(String text) {
return doResolvePlaceholders(text, this.strictHelper);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PlaceholderResolver() {
public String resolvePlaceholder(String placeholderName) {
return getProperty(placeholderName);
}
});
}
}

View File

@@ -16,46 +16,45 @@
package org.springframework.core.env;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import org.springframework.core.convert.ConversionService;
/**
* TODO SPR-7508: document
* Configuration interface to be implemented by most if not all {@link Environment
* Environments}. Provides facilities for setting active and default profiles as well
* as specializing the return types for {@link #getPropertySources()} and
* {@link #getPropertyResolver()} such that they return types that may be manipulated.
*
* @author Chris Beams
* @since 3.1
* @see DefaultEnvironment
* @see org.springframework.context.ConfigurableApplicationContext#getEnvironment
*/
public interface ConfigurableEnvironment extends Environment {
/**
* Specify the set of profiles active for this Environment. Profiles are
* evaluated during container bootstrap to determine whether bean definitions
* should be registered with the container.
*
* @see #setDefaultProfiles
* @see org.springframework.context.annotation.Profile
* @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
*/
void setActiveProfiles(String... profiles);
/**
* Specify the set of profiles to be made active by default if no other profiles
* are explicitly made active through {@link #setActiveProfiles}.
* @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
*/
void setDefaultProfiles(String... profiles);
public ConversionService getConversionService();
public void setConversionService(ConversionService conversionService);
void addPropertySource(PropertySource<?> propertySource);
void addPropertySource(String name, Properties properties);
void addPropertySource(String name, Map<String, String> propertiesMap);
/**
* Return the {@link PropertySources} for this environment in mutable form
*/
MutablePropertySources getPropertySources();
/**
* TODO: SPR-7508 document
*
* Care should be taken to ensure duplicates are not introduced.
*
* Recommend using {@link LinkedList#set(int, Object)} for replacing items,
* and combining {@link LinkedList#remove()} with other methods like
* {@link LinkedList#add(Object)} to prevent duplicates.
*
* Explain how {@link PropertySource#equals(Object)} and hashCode work, and that
* recommend using {@link PropertySource#named(String)} for lookups in the list.
* Return the {@link PropertyResolver} for this environment in configurable form
*/
LinkedList<PropertySource<?>> getPropertySources();
ConfigurablePropertyResolver getPropertyResolver();
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2010 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 org.springframework.core.convert.ConversionService;
/**
* Configuration interface to be implemented by most if not all {@link PropertyResolver
* PropertyResolvers}. Provides facilities for accessing and customizing the
* {@link ConversionService} used when converting property values from one type to
* another.
*
* @author Chris Beams
* @since 3.1
*/
public interface ConfigurablePropertyResolver extends PropertyResolver {
/**
* @return the {@link ConversionService} used when performing type
* conversions on properties.
* @see PropertyResolver#getProperty(String, Class)
*/
ConversionService getConversionService();
/**
* Set the {@link ConversionService} to be used when performing type
* conversions on properties.
* @see PropertyResolver#getProperty(String, Class)
*/
void setConversionService(ConversionService conversionService);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -16,24 +16,85 @@
package org.springframework.core.env;
/**
* TODO SPR-7508: document
* Default implementation of the {@link Environment} interface. Used throughout all non-Web*
* ApplicationContext implementations.
*
* Explain why the default ordering of property sources is the way it is.
* <p>In addition to the usual functions of a {@link ConfigurableEnvironment} such as property
* resolution and profile-related operations, this implementation configures two default property
* sources, to be searched in the following order:
* <ol>
* <li>{@linkplain AbstractEnvironment#getSystemProperties() system properties}
* <li>{@linkplain AbstractEnvironment#getSystemEnvironment() system environment variables}
* </ol>
*
* That is, if the key "xyz" is present both in the JVM system properties as well as in the
* set of environment variables for the current process, the value of key "xyz" from system properties
* will return from a call to {@code environment.getPropertyResolver().getProperty("xyz")}.
* This ordering is chosen by default because system properties are per-JVM, while environment
* variables may be the same across many JVMs on a given system. Giving system properties
* precedence allows for overriding of environment variables on a per-JVM basis.
*
* <p>These default property sources may be removed, reordered, or replaced; and additional
* property sources may be added using the {@link MutablePropertySources} instance available
* from {@link #getPropertySources()}.
*
* <h4>Example: adding a new property source with highest search priority</h4>
* <pre class="code">
* ConfigurableEnvironment environment = new DefaultEnvironment();
* MutablePropertySources propertySources = environment.getPropertySources();
* Map<String, String> myMap = new HashMap<String, String>();
* myMap.put("xyz", "myValue");
* propertySources.addFirst(new MapPropertySource("MY_MAP", myMap));
* </pre>
*
* <h4>Example: removing the default system properties property source</h4>
* <pre class="code">
* MutablePropertySources propertySources = environment.getPropertySources();
* propertySources.remove(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)
* </pre>
*
* <h4>Example: mocking the system environment for testing purposes</h4>
* <pre class="code">
* MutablePropertySources propertySources = environment.getPropertySources();
* MockPropertySource mockEnvVars = new MockPropertySource().withProperty("xyz", "myValue");
* propertySources.replace(DefaultEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);
* </pre>
*
* When an {@link Environment} is being used by an ApplicationContext, it is important
* that any such PropertySource manipulations be performed <em>before</em> the context's {@link
* org.springframework.context.support.AbstractApplicationContext#refresh() refresh()} method is
* called. This ensures that all PropertySources are available during the container bootstrap process,
* including use by {@link org.springframework.context.support.PropertySourcesPlaceholderConfigurer
* property placeholder configurers}.
*
* @author Chris Beams
* @since 3.1
* @see ConfigurableEnvironment
* @see org.springframework.web.context.support.DefaultWebEnvironment
*/
public class DefaultEnvironment extends AbstractEnvironment {
/** System environment property source name: {@value} */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value} */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
/**
* Create a new {@code Environment} populated with property sources in the following order:
* <ul>
* <li>{@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME}
* <li>{@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}
* </ul>
*
* <p>Properties present in {@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME} will
* take precedence over those in {@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}.
*/
public DefaultEnvironment() {
addPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment());
addPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties());
this.getPropertySources().addFirst(new MapPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, this.getSystemEnvironment()));
this.getPropertySources().addFirst(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, this.getSystemProperties()));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -17,98 +17,131 @@
package org.springframework.core.env;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* TODO SPR-7508: document
* TODO: Consider extracting a PropertyResolutionService interface
* Interface representing the environment in which the current application is running.
* Models two key aspects of the application environment:
* <ol>
* <li>profiles</li>
* <li>properties</li>
* </ol>
*
* A <em>profile</em> is a named, logical group of bean definitions to be registered with the
* container only if the given profile is <em>active</em>. Beans may be assigned to a profile
* whether defined in XML or annotations; see the spring-beans 3.1 schema or the {@link
* org.springframework.context.annotation.Profile @Profile} annotation for syntax details.
* The role of the Environment object with relation to profiles is in determining which profiles
* (if any) are currently {@linkplain #getActiveProfiles active}, and which profiles (if any)
* should be {@linkplain #getDefaultProfiles active by default}.
*
* <p><em>Properties</em> play an important role in almost all applications, and may originate
* from a variety of sources: properties files, JVM system properties, system environment
* variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on.
* The role of the environment object with relation to properties is to provide the user with a
* convenient service interface for configuring property sources and resolving properties from them.
*
* <p>Beans managed within an ApplicationContext may register to be {@link
* org.springframework.context.EnvironmentAware EnvironmentAware}, where they can query profile state
* or resolve properties directly.
*
* <p>More commonly, beans will not interact with the Environment directly, but will have ${...}
* property values replaced by a property placeholder configurer such as {@link
* org.springframework.context.support.PropertySourcesPlaceholderConfigurer
* PropertySourcesPlaceholderConfigurer}, which itself is EnvironmentAware, and as of Spring 3.1 is
* registered by default when using {@code <context:property-placeholder/>}.
*
* <p>Configuration of the environment object must be done through the {@link ConfigurableEnvironment}
* interface, returned from all AbstractApplicationContext subclass getEnvironment() methods. See
* {@link DefaultEnvironment} for several examples of using the ConfigurableEnvironment interface
* to manipulate property sources prior to application context refresh().
*
* @author Chris Beams
* @since 3.1
* @see EnvironmentCapable
* @see ConfigurableEnvironment
* @see DefaultEnvironment
* @see org.springframework.context.EnvironmentAware
* @see org.springframework.context.ConfigurableApplicationContext#getEnvironment
* @see org.springframework.context.ConfigurableApplicationContext#setEnvironment
* @see org.springframework.context.support.AbstractApplicationContext#createEnvironment
*/
public interface Environment {
/**
* TODO SPR-7508: document
* Return the set of profiles explicitly made active for this environment. Profiles are used for
* creating logical groupings of bean definitions to be registered conditionally, often based on
* deployment environment. Profiles can be activated by setting {@linkplain
* AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME "spring.profiles.active"} as a system property
* or by calling {@link ConfigurableEnvironment#setActiveProfiles(String...)}.
*
* <p>If no profiles have explicitly been specified as active, then any 'default' profiles will implicitly
* be considered active.
*
* @see #getDefaultProfiles
* @see ConfigurableEnvironment#setActiveProfiles
* @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
*/
Set<String> getActiveProfiles();
String[] getActiveProfiles();
/**
* TODO SPR-7508: document
* Return the set of profiles to be active by default when no active profiles have been set explicitly.
*
* @see #getActiveProfiles
* @see ConfigurableEnvironment#setDefaultProfiles
*/
Set<String> getDefaultProfiles();
String[] getDefaultProfiles();
/**
* TODO SPR-7508: document
* returns true if:
* a) one or more of specifiedProfiles are active in the given environment - see {@link #getActiveProfiles()}
* b) specifiedProfiles contains default profile - see {@link #getDefaultProfile()}
* @return whether one or more of the given profiles is active, or in the case of no explicit active
* profiles, whether one or more of the given profiles is included in the set of default profiles
* @throws IllegalArgumentException unless at least one profile has been specified
* @throws IllegalArgumentException if any profile is the empty string or consists only of whitespace
* @see #getActiveProfiles
* @see #getDefaultProfiles
*/
boolean acceptsProfiles(String[] specifiedProfiles);
boolean acceptsProfiles(String... profiles);
/**
* TODO SPR-7508: document
* @return the {@link PropertyResolver} used for accessing properties.
* @see PropertyResolver
* @see #getPropertySources
*/
boolean containsProperty(String key);
PropertyResolver getPropertyResolver();
/**
* TODO SPR-7508: document
* Return the set of {@link PropertySource} objects used by by this Environment's PropertyResolver
* @see #getPropertyResolver
*/
String getProperty(String key);
PropertySources getPropertySources();
/**
* TODO SPR-7508: document
*/
<T> T getProperty(String key, Class<T> targetType);
/**
* TODO SPR-7508: document
*/
String getRequiredProperty(String key);
/**
* TODO SPR-7508: document
*/
<T> T getRequiredProperty(String key, Class<T> targetType);
/**
* TODO SPR-7508: document
*/
int getPropertyCount();
/**
* TODO SPR-7508: document
*/
Properties asProperties();
/**
* TODO SPR-7508: document that this returns {@link System#getenv()} if allowed, or
* {@link ReadOnlySystemAttributesMap} if not.
* Return the value of {@link System#getenv()} if allowed by the current {@link SecurityManager},
* otherwise return a map implementation that will attempt to access individual keys using calls to
* {@link System#getenv(String)}.
*
* <p>Note that most {@link Environment} implementations will include this system environment map as
* a default {@link PropertySource} to be searched. Therefore, it is recommended that this method not be
* used directly unless bypassing other property sources is expressly intended.
*
* <p>Calls to {@link Map#get(Object)} on the Map returned will never throw {@link IllegalAccessException};
* in cases where the SecurityManager forbids access to a property, {@code null} will be returned and an
* INFO-level log message will be issued noting the exception.
*/
Map<String, String> getSystemEnvironment();
/**
* TODO SPR-7508: document that this returns {@link System#getProperties()} if allowed, or
* {@link ReadOnlySystemAttributesMap} if not. Actually, always returns
* {@link ReadOnlySystemAttributesMap} now.
* see notes within {@link AbstractEnvironment#getSystemProperties()}
* Return the value of {@link System#getProperties()} if allowed by the current {@link SecurityManager},
* otherwise return a map implementation that will attempt to access individual keys using calls to
* {@link System#getProperty(String)}.
*
* <p>Note that most {@code Environment} implementations will include this system properties map as a
* default {@link PropertySource} to be searched. Therefore, it is recommended that this method not be
* used directly unless bypassing other property sources is expressly intended.
*
* <p>Calls to {@link Map#get(Object)} on the Map returned will never throw {@link IllegalAccessException};
* in cases where the SecurityManager forbids access to a property, {@code null} will be returned and an
* INFO-level log message will be issued noting the exception.
*/
Map<String, String> getSystemProperties();
/**
* TODO SPR-7508: document
* @see #resolveRequiredPlaceholders(String)
* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, int)
*/
String resolvePlaceholders(String text);
/**
* TODO SPR-7508: document
* @see #resolvePlaceholders(String)
* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, int)
*/
String resolveRequiredPlaceholders(String path);
}

View File

@@ -18,15 +18,32 @@ package org.springframework.core.env;
/**
* TODO SPR-7508: document
*
* Interface indicating a component contains and makes available an {@link Environment} object.
*
* <p>All Spring application contexts are EnvironmentCapable, and the interface is used primarily
* for performing {@code instanceof} checks in framework methods that accept BeanFactory
* instances that may or may not actually be ApplicationContext instances in order to interact
* with the environment if indeed it is available.
*
* <p>As mentioned, {@link org.springframework.context.ApplicationContext ApplicationContext}
* extends EnvironmentCapable, and thus exposes a {@link #getEnvironment()} method; however,
* {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext}
* redefines {@link org.springframework.context.ConfigurableApplicationContext#getEnvironment
* getEnvironment()} and narrows the signature to return a {@link ConfigurableEnvironment}. The effect
* is that an Environment object is 'read-only' until it accessed from a ConfigurableApplicationContext,
* at which point it too may be configured.
*
* @author Chris Beams
* @since 3.1
* @see Environment
* @see ConfigurableEnvironmentCapable
* @see ConfigurableEnvironment
* @see org.springframework.context.ConfigurableApplicationContext#getEnvironment
*/
public interface EnvironmentCapable {
/**
* Return the Environment for this object
*/
Environment getEnvironment();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -18,18 +18,12 @@ package org.springframework.core.env;
import java.util.Map;
/**
* TODO SPR-7508: document
*
* Consider adding a TypeConvertingMapPropertySource to accommodate
* non-string keys and values. Could be confusing when used in conjunction
* with Environment.getProperty(), which also does type conversions. If this
* is added, consider renaming this class to SimpleMapPropertySource and
* rename PropertiesPropertySource to SimplePropertiesPropertySource.
* {@link PropertySource} that reads keys and values from a {@code Map<String,String>} object.
*
* @author Chris Beams
* @since 3.1
* @see PropertiesPropertySource
*/
public class MapPropertySource extends PropertySource<Map<String, String>> {
@@ -37,17 +31,14 @@ public class MapPropertySource extends PropertySource<Map<String, String>> {
super(name, source);
}
public boolean containsProperty(String key) {
return source.containsKey(key);
}
public String getProperty(String key) {
return source.get(key);
@Override
public String[] getPropertyNames() {
return this.source.keySet().toArray(EMPTY_NAMES_ARRAY);
}
@Override
public int size() {
return source.size();
public String getProperty(String key) {
return this.source.get(key);
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2002-2010 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.Collections;
import java.util.LinkedList;
import java.util.List;
import org.springframework.util.Assert;
public class MutablePropertySources implements PropertySources {
private final LinkedList<PropertySource<?>> propertySourceList = new LinkedList<PropertySource<?>>();
static final String NON_EXISTENT_PROPERTY_SOURCE_MESSAGE = "PropertySource named [%s] does not exist";
static final String ILLEGAL_RELATIVE_ADDITION_MESSAGE = "PropertySource named [%s] cannot be added relative to itself";
public MutablePropertySources() {
}
public MutablePropertySources(PropertySources propertySources) {
this.addAll(propertySources);
}
public void addAll(PropertySources propertySources) {
for (PropertySource<?> propertySource : propertySources.asList()) {
this.addLast(propertySource);
}
}
public void addFirst(PropertySource<?> propertySource) {
removeIfPresent(propertySource);
this.propertySourceList.addFirst(propertySource);
}
public void addLast(PropertySource<?> propertySource) {
removeIfPresent(propertySource);
this.propertySourceList.addLast(propertySource);
}
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
assertLegalRelativeAddition(relativePropertySourceName, propertySource);
removeIfPresent(propertySource);
int index = assertPresentAndGetIndex(relativePropertySourceName);
addAtIndex(index, propertySource);
}
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
assertLegalRelativeAddition(relativePropertySourceName, propertySource);
removeIfPresent(propertySource);
int index = assertPresentAndGetIndex(relativePropertySourceName);
addAtIndex(index+1, propertySource);
}
protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) {
String newPropertySourceName = propertySource.getName();
Assert.isTrue(!relativePropertySourceName.equals(newPropertySourceName),
String.format(ILLEGAL_RELATIVE_ADDITION_MESSAGE, newPropertySourceName));
}
protected void addAtIndex(int index, PropertySource<?> propertySource) {
removeIfPresent(propertySource);
this.propertySourceList.add(index, propertySource);
}
protected void removeIfPresent(PropertySource<?> propertySource) {
if (this.propertySourceList.contains(propertySource)) {
this.propertySourceList.remove(propertySource);
}
}
public boolean contains(String propertySourceName) {
return propertySourceList.contains(PropertySource.named(propertySourceName));
}
public PropertySource<?> remove(String propertySourceName) {
int index = propertySourceList.indexOf(PropertySource.named(propertySourceName));
if (index >= 0) {
return propertySourceList.remove(index);
}
return null;
}
public void replace(String propertySourceName, PropertySource<?> propertySource) {
int index = assertPresentAndGetIndex(propertySourceName);
this.propertySourceList.set(index, propertySource);
}
protected int assertPresentAndGetIndex(String propertySourceName) {
int index = this.propertySourceList.indexOf(PropertySource.named(propertySourceName));
Assert.isTrue(index >= 0, String.format(NON_EXISTENT_PROPERTY_SOURCE_MESSAGE, propertySourceName));
return index;
}
public int size() {
return propertySourceList.size();
}
public List<PropertySource<?>> asList() {
return Collections.unmodifiableList(this.propertySourceList);
}
public PropertySource<?> get(String propertySourceName) {
return propertySourceList.get(propertySourceList.indexOf(PropertySource.named(propertySourceName)));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -16,39 +16,26 @@
package org.springframework.core.env;
import java.util.Map;
import java.util.Properties;
/**
* TODO SPR-7508: document how this does accept a Properties object,
* which is capable of holding non-string keys and values (because
* Properties is a Hashtable), but is limited to resolving string-based
* keys and values.
* {@link PropertySource} implementation that extracts properties from a {@link java.util.Properties} object.
*
* Consider adding a TypeConvertingPropertiesPropertySource to accommodate
* non-string keys and values (such as is technically possible with
* System.getProperties())
* <p>Note that because a {@code Properties} object is technically an {@code <Object, Object>}
* {@link java.util.Hashtable Hashtable}, one may contain non-{@code String} keys or values. This
* implementation, however is restricted to accessing only {@code String}-based keys and values, in
* the same fashion as {@link Properties#getProperty} and {@link Properties#setProperty}.
*
* @author Chris Beams
* @since 3.1
* @see org.springframework.mock.env.MockPropertySource
*/
public class PropertiesPropertySource extends PropertySource<Properties> {
public class PropertiesPropertySource extends MapPropertySource {
@SuppressWarnings({ "unchecked", "rawtypes" })
public PropertiesPropertySource(String name, Properties source) {
super(name, source);
}
public boolean containsProperty(String key) {
return source.containsKey(key);
}
public String getProperty(String key) {
return source.getProperty(key);
}
@Override
public int size() {
return source.size();
super(name, (Map)source);
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2010 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.Properties;
/**
* Interface for resolving properties against any underlying source.
*
* @author Chris Beams
* @since 3.1
* @see Environment#getPropertyResolver()
*/
public interface PropertyResolver {
/**
* @return whether the given property key is available for resolution
*/
boolean containsProperty(String key);
/**
* @return the property value associated with the given key
* @see #getProperty(String, Class)
*/
String getProperty(String key);
/**
* @return the property value associated with the given key, or {@code null}
* if the key cannot be resolved
*/
<T> T getProperty(String key, Class<T> targetType);
/**
* @return the property value associated with the given key, converted to the given
* targetType (never {@code null})
* @throws IllegalStateException if the key cannot be resolved
* @see #getRequiredProperty(String, Class)
*/
String getRequiredProperty(String key) throws IllegalStateException;
/**
* @return the property value associated with the given key, converted to the given
* targetType (never {@code null})
* @throws IllegalStateException if the given key cannot be resolved
*/
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
/**
* @return the number of unique properties keys resolvable
*/
int getPropertyCount();
/**
* @return all property key/value pairs as a {@link java.util.Properties} instance
*/
Properties asProperties();
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
* property values as resolved by {@link #getProperty}. Unresolvable placeholders with
* no default value are ignored and passed through unchanged.
* @param text the String to resolve
* @return the resolved String (never {@code null})
* @throws IllegalArgumentException if given text is {@code null}
* @see #resolveRequiredPlaceholders
* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String)
*/
String resolvePlaceholders(String text);
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
* property values as resolved by {@link #getProperty}. Unresolvable placeholders with
* no default value will cause an IllegalArgumentException to be thrown.
* @return the resolved String (never {@code null})
* @throws IllegalArgumentException if given text is {@code null}
* @throws IllegalArgumentException if any placeholders are unresolvable
* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, boolean)
*/
String resolveRequiredPlaceholders(String path) throws IllegalArgumentException;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -18,43 +18,122 @@ package org.springframework.core.env;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
/**
* Abstract base class representing a source of key/value property pairs. The underlying
* {@linkplain #getSource() source object} may be of any type {@code T} that encapsulates
* properties. Examples include {@link java.util.Properties} objects, {@link java.util.Map}
* objects, {@code ServletContext} and {@code ServletConfig} objects (for access to init parameters).
* Explore the {@code PropertySource} type hierarchy to see provided implementations.
*
* <p>{@code PropertySource} objects are not typically used in isolation, but rather through a
* {@link PropertySources} object, which aggregates property sources and in conjunction with
* a {@link PropertyResolver} implementation that can perform precedence-based searches across
* the set of {@code PropertySources}.
*
* <p>{@code PropertySource} identity is determined not based on the content of encapsulated
* properties, but rather based on the {@link #getName() name} of the {@code PropertySource}
* alone. This is useful for manipulating {@code PropertySource} objects when in collection
* contexts. See operations in {@link MutablePropertySources} as well as the
* {@link #named(String)} and {@link #toString()} methods for details.
*
* @author Chris Beams
* @since 3.1
* @see PropertySources
* @see PropertyResolver
* @see PropertySourcesPropertyResolver
* @see MutablePropertySources
*/
public abstract class PropertySource<T> {
protected static final String[] EMPTY_NAMES_ARRAY = new String[0];
protected final Log logger = LogFactory.getLog(this.getClass());
protected final String name;
protected final T source;
/**
* Create a new {@code PropertySource} with the given name and source object.
*/
public PropertySource(String name, T source) {
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
/**
* Return the name of this {@code PropertySource}
*/
public String getName() {
return name;
return this.name;
}
/**
* Return the underlying source object for this {@code PropertySource}.
*/
public T getSource() {
return source;
}
public abstract boolean containsProperty(String key);
/**
* Return the names of all properties contained by the {@linkplain #getSource() source}
* object (never {@code null}).
*/
public abstract String[] getPropertyNames();
/**
* Return the value associated with the given key, {@code null} if not found.
* @param key the property key to find
* @see PropertyResolver#getRequiredProperty(String)
*/
public abstract String getProperty(String key);
public abstract int size();
/**
* Return whether this {@code PropertySource} contains a property with the given key.
* @param key the property key to find
*/
public boolean containsProperty(String name) {
Assert.notNull(name, "property name must not be null");
for (String candidate : this.getPropertyNames()) {
if (candidate.equals(name)) {
return true;
}
}
return false;
}
/**
* Return the number of unique property keys available to this {@code PropertySource}.
*/
public int size() {
return this.getPropertyNames().length;
}
/**
* Return a hashcode derived from the {@code name} property of this {@code PropertySource}
* object.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
return result;
}
/**
* This {@code PropertySource} object is equal to the given object if:
* <ul>
* <li>they are the same instance
* <li>the {@code name} properties for both objects are equal
* </ul>
*
* <P>No properties other than {@code name} are evaluated.
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -64,10 +143,10 @@ public abstract class PropertySource<T> {
if (!(obj instanceof PropertySource))
return false;
PropertySource<?> other = (PropertySource<?>) obj;
if (name == null) {
if (this.name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
} else if (!this.name.equals(other.name))
return false;
return true;
}
@@ -87,17 +166,35 @@ public abstract class PropertySource<T> {
public String toString() {
if (logger.isDebugEnabled()) {
return String.format("%s@%s [name='%s', properties=%s]",
getClass().getSimpleName(), System.identityHashCode(this), name, source);
this.getClass().getSimpleName(), System.identityHashCode(this), this.name, this.source);
}
return String.format("%s [name='%s', propertyCount=%d]",
getClass().getSimpleName(), name, this.size());
this.getClass().getSimpleName(), this.name, this.size());
}
/**
* For collection comparison purposes
* TODO SPR-7508: document
* Return a {@code PropertySource} implementation intended for collection comparison purposes only.
*
* <p>Primarily for internal use, but given a collection of {@code PropertySource} objects, may be
* used as follows:
* <pre class="code">
* {@code
* List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>();
* sources.add(new MapPropertySource("sourceA", mapA));
* sources.add(new MapPropertySource("sourceB", mapB));
* assert sources.contains(PropertySource.named("sourceA"));
* assert sources.contains(PropertySource.named("sourceB"));
* assert !sources.contains(PropertySource.named("sourceC"));
* }
* </pre>
*
* <p>The returned {@code PropertySource} will throw {@code UnsupportedOperationException}
* if any methods other than {@code equals(Object)}, {@code hashCode()}, and {@code toString()}
* are called.
*
* @param name the name of the comparison {@code PropertySource} to be created and returned.
*/
public static PropertySource<?> named(String name) {
return new ComparisonPropertySource(name);
@@ -105,35 +202,69 @@ public abstract class PropertySource<T> {
/**
* TODO: SPR-7508: document
* {@code PropertySource} to be used as a placeholder in cases where an actual
* property source cannot be eagerly initialized at application context
* creation time. For example, a {@code ServletContext}-based property source
* must wait until the {@code ServletContext} object is available to its enclosing
* {@code ApplicationContext}. In such cases, a stub should be used to hold the
* intended default position/order of the property source, then be replaced
* during context refresh.
*
* @see org.springframework.context.support.AbstractApplicationContext#initPropertySources()
* @see org.springframework.web.context.support.DefaultWebEnvironment
* @see org.springframework.web.context.support.ServletContextPropertySource
*/
public static class ComparisonPropertySource extends PropertySource<Void>{
public static class StubPropertySource extends PropertySource<Object> {
public StubPropertySource(String name) {
super(name, new Object());
}
@Override
public String getProperty(String key) {
// TODO SPR-7408: logging
return null;
}
@Override
public String[] getPropertyNames() {
return EMPTY_NAMES_ARRAY;
}
}
/**
* @see PropertySource#named(String)
*/
static class ComparisonPropertySource extends StubPropertySource {
private static final String USAGE_ERROR =
"ComparisonPropertySource instances are for collection comparison " +
"use only";
public ComparisonPropertySource(String name) {
super(name, null);
super(name);
}
@Override
public Void getSource() {
public Object getSource() {
throw new UnsupportedOperationException(USAGE_ERROR);
}
@Override
public String[] getPropertyNames() {
throw new UnsupportedOperationException(USAGE_ERROR);
}
@Override
public String getProperty(String key) {
throw new UnsupportedOperationException(USAGE_ERROR);
}
public boolean containsProperty(String key) {
throw new UnsupportedOperationException(USAGE_ERROR);
}
public int size() {
throw new UnsupportedOperationException(USAGE_ERROR);
}
@Override
public String toString() {
return String.format("%s [name='%s']", getClass().getSimpleName(), name);
return String.format("%s [name='%s']", getClass().getSimpleName(), this.name);
}
}
}

View File

@@ -16,16 +16,17 @@
package org.springframework.core.env;
import java.util.List;
/**
* TODO SPR-7508: document
*
* @author Chris Beams
* @since 3.1
*/
public class DefaultWebEnvironment extends DefaultEnvironment {
public static final String SERVLET_CONTEXT_PARAMS_PROPERTY_SOURCE_NAME = "servletContextInitParams";
public static final String SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
public interface PropertySources {
PropertySource<?> get(String propertySourceName);
List<PropertySource<?>> asList();
int size();
boolean contains(String propertySourceName);
}

View File

@@ -0,0 +1,109 @@
/*
* 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 static java.lang.String.format;
import java.util.List;
import java.util.Properties;
/**
* {@link PropertyResolver} implementation that resolves property values against
* an underlying set of {@link PropertySources}.
*
* @author Chris Beams
* @since 3.1
*/
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
private final PropertySources propertySources;
/**
* Create a new resolver against the given property sources.
* @param propertySources the set of {@link PropertySource} objects to use
*/
public PropertySourcesPropertyResolver(PropertySources propertySources) {
this.propertySources = propertySources;
}
public boolean containsProperty(String key) {
for (PropertySource<?> propertySource : this.propertySources.asList()) {
if (propertySource.containsProperty(key)) {
return true;
}
}
return false;
}
public String getProperty(String key) {
if (logger.isTraceEnabled()) {
logger.trace(format("getProperty(\"%s\") (implicit targetType [String])", key));
}
return this.getProperty(key, String.class);
}
public <T> T getProperty(String key, Class<T> targetValueType) {
boolean debugEnabled = logger.isDebugEnabled();
if (logger.isTraceEnabled()) {
logger.trace(format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName()));
}
for (PropertySource<?> propertySource : this.propertySources.asList()) {
if (debugEnabled) {
logger.debug(format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
if (propertySource.containsProperty(key)) {
Object value = propertySource.getProperty(key);
Class<?> valueType = value == null ? null : value.getClass();
if (debugEnabled) {
logger.debug(
format("Found key '%s' in [%s] with type [%s] and value '%s'",
key, propertySource.getName(),
valueType == null ? "" : valueType.getSimpleName(), value));
}
if (value == null) {
return null;
}
if (!this.conversionService.canConvert(valueType, targetValueType)) {
throw new IllegalArgumentException(
format("Cannot convert value [%s] from source type [%s] to target type [%s]",
value, valueType.getSimpleName(), targetValueType.getSimpleName()));
}
return conversionService.convert(value, targetValueType);
}
}
if (debugEnabled) {
logger.debug(format("Could not find key '%s' in any property source. Returning [null]", key));
}
return null;
}
public Properties asProperties() {
Properties mergedProps = new Properties();
List<PropertySource<?>> propertySourcesList = this.propertySources.asList();
for (int i = propertySourcesList.size() -1; i >= 0; i--) {
PropertySource<?> source = propertySourcesList.get(i);
for (String key : source.getPropertyNames()) {
mergedProps.put(key, source.getProperty(key));
}
}
return mergedProps;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* 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.
@@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.springframework.util.Assert;
/**
* Read-only {@code Map<String, String>} implementation that is backed by system properties or environment
@@ -38,32 +39,16 @@ abstract class ReadOnlySystemAttributesMap implements Map<String, String> {
return get(key) != null;
}
/**
* @param key the name of the system attribute to retrieve
* @throws IllegalArgumentException if given key is non-String
*/
public String get(Object key) {
if (key instanceof String) {
String attributeName = (String) key;
return getSystemAttribute(attributeName);
}
else {
// TODO SPR-7508: technically breaks backward-compat. Used to return null
// for non-string keys, now throws. Any callers who have coded to this
// behavior will now break. It's highly unlikely, however; could be
// a calculated risk to take. Throwing is a better choice, as returning
// null represents a 'false negative' - it's not actually that the key
// isn't present, it's simply that you cannot access it through the current
// abstraction. Remember, this case would only come up if (a) there are
// non-string keys or values in system properties, (b) there is a
// SecurityManager present, and (c) the user attempts to access one
// of those properties through this abstraction. This combination is
// probably unlikely enough to merit the change.
//
// note also that the previous implementation didn't consider the
// possibility of non-string values the anonymous implementation used
// for System properties access now does.
//
// See AbstractEnvironment for relevant anonymous implementations
// See DefaultEnvironmentTests for unit tests around these cases
throw new IllegalStateException("TODO SPR-7508: message");
}
Assert.isInstanceOf(String.class, key,
String.format("expected key [%s] to be of type String, got %s",
key, key.getClass().getName()));
return this.getSystemAttribute((String) key);
}
public boolean isEmpty() {

View File

@@ -21,16 +21,16 @@ import java.io.IOException;
import org.springframework.core.env.DefaultEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link java.beans.PropertyEditor Editor} for {@link Resource}
* descriptors, to automatically convert <code>String</code> locations
* e.g. <code>"file:C:/myfile.txt"</code> or
* <code>"classpath:myfile.txt"</code>) to <code>Resource</code>
* properties instead of using a <code>String</code> location property.
* descriptors, to automatically convert {@code String} locations
* e.g. {@code file:C:/myfile.txt} or {@code classpath:myfile.txt} to
* {@code Resource} properties instead of using a {@code String} location property.
*
* <p>The path may contain <code>${...}</code> placeholders, to be
* resolved as {@link Environment} properties: e.g. <code>${user.dir}</code>.
@@ -46,7 +46,7 @@ import org.springframework.util.StringUtils;
* @see Resource
* @see ResourceLoader
* @see DefaultResourceLoader
* @see org.springframework.env.Environment#resolvePlaceholders
* @see Environment#resolvePlaceholders
*/
public class ResourceEditor extends PropertyEditorSupport {
@@ -137,9 +137,10 @@ public class ResourceEditor extends PropertyEditorSupport {
* @see Environment#resolveRequiredPlaceholders
*/
protected String resolvePath(String path) {
PropertyResolver resolver = environment.getPropertyResolver();
return this.ignoreUnresolvablePlaceholders ?
environment.resolvePlaceholders(path) :
environment.resolveRequiredPlaceholders(path);
resolver.resolvePlaceholders(path) :
resolver.resolveRequiredPlaceholders(path);
}

View File

@@ -27,6 +27,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.DefaultEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.io.Resource;
/**
@@ -196,9 +197,10 @@ public class ResourceArrayPropertyEditor extends PropertyEditorSupport {
* @see Environment#resolveRequiredPlaceholders
*/
protected String resolvePath(String path) {
PropertyResolver resolver = environment.getPropertyResolver();
return this.ignoreUnresolvablePlaceholders ?
environment.resolvePlaceholders(path) :
environment.resolveRequiredPlaceholders(path);
resolver.resolvePlaceholders(path) :
resolver.resolveRequiredPlaceholders(path);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -16,6 +16,7 @@
package org.springframework.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
@@ -301,7 +302,20 @@ public abstract class CollectionUtils {
}
/**
* Adapts an enumeration to an iterator.
* Marshal the elements from the given enumeration into an array of the given type.
* Enumeration elements must be assignable to the type of the given array. The array
* returned will be a different instance than the array given.
*/
public static <A,E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) {
ArrayList<A> elements = new ArrayList<A>();
while (enumeration.hasMoreElements()) {
elements.add(enumeration.nextElement());
}
return elements.toArray(array);
}
/**
* Adapt an enumeration to an iterator.
* @param enumeration the enumeration
* @return the iterator
*/

View File

@@ -25,12 +25,6 @@ import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
* <code>${user.dir}</code>. Default values can be supplied using the ":" separator between key
* and value.
*
* TODO SPR-7508: review item - nearly all uses of {@link SystemPropertyUtils#resolvePlaceholders(String)}
* have been replaced by Environment#resolvePlaceholder(), however, there are several locations in the
* framework that cannot be so refactored as referring to Environment would introduce a cycle. Case in point
* Log4JConfigurer and Log4JWebConfigurer. Need to unify this functionality one way or another. It's
* currently pure duplication.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Dave Syer

View File

@@ -16,412 +16,22 @@
package org.springframework.core.env;
import java.lang.reflect.Field;
import java.security.AccessControlException;
import java.security.Permission;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.List;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.internal.matchers.TypeSafeMatcher;
import static java.lang.String.format;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.*;
import static org.springframework.core.env.AbstractEnvironment.*;
import static org.springframework.core.env.DefaultEnvironmentTests.CollectionMatchers.*;
/**
* Unit tests for {@link DefaultEnvironment}.
*
* @author Chris Beams
*/
public class DefaultEnvironmentTests {
private static final String ALLOWED_PROPERTY_NAME = "theanswer";
private static final String ALLOWED_PROPERTY_VALUE = "42";
private static final String DISALLOWED_PROPERTY_NAME = "verboten";
private static final String DISALLOWED_PROPERTY_VALUE = "secret";
private static final String STRING_PROPERTY_NAME = "stringPropName";
private static final String STRING_PROPERTY_VALUE = "stringPropValue";
private static final Object NON_STRING_PROPERTY_NAME = new Object();
private static final Object NON_STRING_PROPERTY_VALUE = new Object();
private ConfigurableEnvironment environment;
private Properties testProperties;
@Before
public void setUp() {
environment = new DefaultEnvironment();
testProperties = new Properties();
environment.addPropertySource("testProperties", testProperties);
}
@Test @SuppressWarnings({ "unchecked", "rawtypes", "serial" })
public void getPropertySources_manipulatePropertySourceOrder() {
AbstractEnvironment env = new AbstractEnvironment() { };
env.addPropertySource("system", new HashMap() {{ put("foo", "systemValue"); }});
env.addPropertySource("local", new HashMap() {{ put("foo", "localValue"); }});
// 'local' was added (pushed) last so has precedence
assertThat(env.getProperty("foo"), equalTo("localValue"));
// put 'system' at the front of the list
LinkedList<PropertySource<?>> propertySources = env.getPropertySources();
propertySources.addFirst(propertySources.remove(propertySources.indexOf(PropertySource.named("system"))));
// 'system' now has precedence
assertThat(env.getProperty("foo"), equalTo("systemValue"));
assertThat(propertySources.size(), is(2));
}
@Test @SuppressWarnings({ "unchecked", "rawtypes", "serial" })
public void getPropertySources_replacePropertySource() {
AbstractEnvironment env = new AbstractEnvironment() { };
env.addPropertySource("system", new HashMap() {{ put("foo", "systemValue"); }});
env.addPropertySource("local", new HashMap() {{ put("foo", "localValue"); }});
// 'local' was added (pushed) last so has precedence
assertThat(env.getProperty("foo"), equalTo("localValue"));
// replace 'local' with new property source
LinkedList<PropertySource<?>> propertySources = env.getPropertySources();
int localIndex = propertySources.indexOf(PropertySource.named("local"));
MapPropertySource newSource = new MapPropertySource("new", new HashMap() {{ put("foo", "newValue"); }});
propertySources.set(localIndex, newSource);
// 'system' now has precedence
assertThat(env.getProperty("foo"), equalTo("newValue"));
assertThat(propertySources.size(), is(2));
}
@Test
public void getProperty() {
assertThat(environment.getProperty("foo"), nullValue());
testProperties.put("foo", "bar");
assertThat(environment.getProperty("foo"), is("bar"));
}
@Test
public void getProperty_withExplicitNullValue() {
// java.util.Properties does not allow null values (because Hashtable does not)
Map<String, String> nullableProperties = new HashMap<String, String>();
environment.addPropertySource("nullableProperties", nullableProperties);
nullableProperties.put("foo", null);
assertThat(environment.getProperty("foo"), nullValue());
}
@Test
public void getProperty_withStringArrayConversion() {
testProperties.put("foo", "bar,baz");
assertThat(environment.getProperty("foo", String[].class), equalTo(new String[] { "bar", "baz" }));
}
@Test
public void getProperty_withNonConvertibleTargetType() {
testProperties.put("foo", "bar");
class TestType { }
try {
environment.getProperty("foo", TestType.class);
fail("Expected IllegalArgumentException due to non-convertible types");
} catch (IllegalArgumentException ex) {
// expected
}
}
@Test
public void getRequiredProperty() {
testProperties.put("exists", "xyz");
assertThat(environment.getRequiredProperty("exists"), is("xyz"));
try {
environment.getRequiredProperty("bogus");
fail("expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// expected
}
}
@Test
public void getRequiredProperty_withStringArrayConversion() {
testProperties.put("exists", "abc,123");
assertThat(environment.getRequiredProperty("exists", String[].class), equalTo(new String[] { "abc", "123" }));
try {
environment.getRequiredProperty("bogus", String[].class);
fail("expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// expected
}
}
@Test @SuppressWarnings({ "rawtypes", "serial", "unchecked" })
public void asProperties() {
ConfigurableEnvironment env = new AbstractEnvironment() { };
assertThat(env.asProperties(), notNullValue());
env.addPropertySource("lowestPrecedence", new HashMap() {{ put("common", "lowCommon"); put("lowKey", "lowVal"); }});
env.addPropertySource("middlePrecedence", new HashMap() {{ put("common", "midCommon"); put("midKey", "midVal"); }});
env.addPropertySource("highestPrecedence", new HashMap() {{ put("common", "highCommon"); put("highKey", "highVal"); }});
Properties props = env.asProperties();
assertThat(props.getProperty("common"), is("highCommon"));
assertThat(props.getProperty("lowKey"), is("lowVal"));
assertThat(props.getProperty("midKey"), is("midVal"));
assertThat(props.getProperty("highKey"), is("highVal"));
assertThat(props.size(), is(4));
}
@Test
public void activeProfiles() {
assertThat(environment.getActiveProfiles(), isEmpty());
environment.setActiveProfiles("local", "embedded");
Set<String> activeProfiles = environment.getActiveProfiles();
assertThat(activeProfiles, hasItems("local", "embedded"));
assertThat(activeProfiles.size(), is(2));
try {
environment.getActiveProfiles().add("bogus");
fail("activeProfiles should be unmodifiable");
} catch (UnsupportedOperationException ex) {
// expected
}
environment.setActiveProfiles("foo");
assertThat(activeProfiles, hasItem("foo"));
assertThat(environment.getActiveProfiles().size(), is(1));
}
@Test
public void systemPropertiesEmpty() {
assertThat(environment.getActiveProfiles(), isEmpty());
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "");
assertThat(environment.getActiveProfiles(), isEmpty());
System.getProperties().remove(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Test
public void systemPropertiesResoloutionOfProfiles() {
assertThat(environment.getActiveProfiles(), isEmpty());
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "foo");
assertThat(environment.getActiveProfiles(), hasItem("foo"));
// clean up
System.getProperties().remove(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Test
public void systemPropertiesResoloutionOfMultipleProfiles() {
assertThat(environment.getActiveProfiles(), isEmpty());
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "foo,bar");
assertThat(environment.getActiveProfiles(), hasItems("foo", "bar"));
System.getProperties().remove(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Test
public void systemPropertiesResolutionOfMulitpleProfiles_withWhitespace() {
assertThat(environment.getActiveProfiles(), isEmpty());
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, " bar , baz "); // notice whitespace
assertThat(environment.getActiveProfiles(), hasItems("bar", "baz"));
System.getProperties().remove(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Test
public void environmentResolutionOfDefaultSpringProfileProperty_noneSet() {
assertThat(environment.getDefaultProfiles(), isEmpty());
}
@Test
public void environmentResolutionOfDefaultSpringProfileProperty_isSet() {
testProperties.setProperty(DEFAULT_PROFILES_PROPERTY_NAME, "custom-default");
assertTrue(environment.getDefaultProfiles().contains("custom-default"));
}
@Test
public void systemPropertiesAccess() {
System.setProperty(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
System.setProperty(DISALLOWED_PROPERTY_NAME, DISALLOWED_PROPERTY_VALUE);
System.getProperties().put(STRING_PROPERTY_NAME, NON_STRING_PROPERTY_VALUE);
System.getProperties().put(NON_STRING_PROPERTY_NAME, STRING_PROPERTY_VALUE);
{
Map<?, ?> systemProperties = environment.getSystemProperties();
assertThat(systemProperties, notNullValue());
assertSame(systemProperties, System.getProperties());
assertThat(systemProperties.get(ALLOWED_PROPERTY_NAME), equalTo((Object)ALLOWED_PROPERTY_VALUE));
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME), equalTo((Object)DISALLOWED_PROPERTY_VALUE));
// non-string keys and values work fine... until the security manager is introduced below
assertThat(systemProperties.get(STRING_PROPERTY_NAME), equalTo(NON_STRING_PROPERTY_VALUE));
assertThat(systemProperties.get(NON_STRING_PROPERTY_NAME), equalTo((Object)STRING_PROPERTY_VALUE));
}
SecurityManager oldSecurityManager = System.getSecurityManager();
SecurityManager securityManager = new SecurityManager() {
@Override
public void checkPropertiesAccess() {
// see http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperties()
throw new AccessControlException("Accessing the system properties is disallowed");
}
@Override
public void checkPropertyAccess(String key) {
// see http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperty(java.lang.String)
if (DISALLOWED_PROPERTY_NAME.equals(key)) {
throw new AccessControlException(
format("Accessing the system property [%s] is disallowed", DISALLOWED_PROPERTY_NAME));
}
}
@Override
public void checkPermission(Permission perm) {
// allow everything else
}
};
System.setSecurityManager(securityManager);
{
Map<?, ?> systemProperties = environment.getSystemProperties();
assertThat(systemProperties, notNullValue());
assertThat(systemProperties, instanceOf(ReadOnlySystemAttributesMap.class));
assertThat((String)systemProperties.get(ALLOWED_PROPERTY_NAME), equalTo(ALLOWED_PROPERTY_VALUE));
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME), equalTo(null));
// nothing we can do here in terms of warning the user that there was
// actually a (non-string) value available. By this point, we only
// have access to calling System.getProperty(), which itself returns null
// if the value is non-string. So we're stuck with returning a potentially
// misleading null.
assertThat(systemProperties.get(STRING_PROPERTY_NAME), nullValue());
// in the case of a non-string *key*, however, we can do better. Alert
// the user that under these very special conditions (non-object key +
// SecurityManager that disallows access to system properties), they
// cannot do what they're attempting.
try {
systemProperties.get(NON_STRING_PROPERTY_NAME);
fail("Expected IllegalStateException when searching with non-string key against ReadOnlySystemAttributesMap");
} catch (IllegalStateException ex) {
// expected
}
}
System.setSecurityManager(oldSecurityManager);
System.clearProperty(ALLOWED_PROPERTY_NAME);
System.clearProperty(DISALLOWED_PROPERTY_NAME);
System.getProperties().remove(STRING_PROPERTY_NAME);
System.getProperties().remove(NON_STRING_PROPERTY_NAME);
}
@Test
public void systemEnvironmentAccess() throws Exception {
getModifiableSystemEnvironment().put(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
getModifiableSystemEnvironment().put(DISALLOWED_PROPERTY_NAME, DISALLOWED_PROPERTY_VALUE);
{
Map<String, String> systemEnvironment = environment.getSystemEnvironment();
assertThat(systemEnvironment, notNullValue());
assertSame(systemEnvironment, System.getenv());
}
SecurityManager oldSecurityManager = System.getSecurityManager();
SecurityManager securityManager = new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
//see http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getenv()
if ("getenv.*".equals(perm.getName())) {
throw new AccessControlException("Accessing the system environment is disallowed");
}
//see http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getenv(java.lang.String)
if (("getenv."+DISALLOWED_PROPERTY_NAME).equals(perm.getName())) {
throw new AccessControlException(
format("Accessing the system environment variable [%s] is disallowed", DISALLOWED_PROPERTY_NAME));
}
}
};
System.setSecurityManager(securityManager);
{
Map<String, String> systemEnvironment = environment.getSystemEnvironment();
assertThat(systemEnvironment, notNullValue());
assertThat(systemEnvironment, instanceOf(ReadOnlySystemAttributesMap.class));
assertThat(systemEnvironment.get(ALLOWED_PROPERTY_NAME), equalTo(ALLOWED_PROPERTY_VALUE));
assertThat(systemEnvironment.get(DISALLOWED_PROPERTY_NAME), nullValue());
}
System.setSecurityManager(oldSecurityManager);
getModifiableSystemEnvironment().remove(ALLOWED_PROPERTY_NAME);
getModifiableSystemEnvironment().remove(DISALLOWED_PROPERTY_NAME);
}
@Test
public void resolvePlaceholders() {
AbstractEnvironment env = new AbstractEnvironment() { };
Properties testProperties = new Properties();
testProperties.setProperty("foo", "bar");
env.addPropertySource("testProperties", testProperties);
String resolved = env.resolvePlaceholders("pre-${foo}-${unresolvable}-post");
assertThat(resolved, is("pre-bar-${unresolvable}-post"));
}
@Test
public void resolveRequiredPlaceholders() {
AbstractEnvironment env = new AbstractEnvironment() { };
Properties testProperties = new Properties();
testProperties.setProperty("foo", "bar");
env.addPropertySource("testProperties", testProperties);
try {
env.resolveRequiredPlaceholders("pre-${foo}-${unresolvable}-post");
fail("expected exception");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), is("Could not resolve placeholder 'unresolvable'"));
}
}
public static class CollectionMatchers {
public static Matcher<Collection<?>> isEmpty() {
return new TypeSafeMatcher<Collection<?>>() {
@Override
public boolean matchesSafely(Collection<?> collection) {
return collection.isEmpty();
}
public void describeTo(Description desc) {
desc.appendText("an empty collection");
}
};
}
}
// TODO SPR-7508: duplicated from EnvironmentPropertyResolutionSearchTests
@SuppressWarnings("unchecked")
private static Map<String, String> getModifiableSystemEnvironment() throws Exception {
Class<?>[] classes = Collections.class.getDeclaredClasses();
Map<String, String> systemEnv = System.getenv();
for (Class<?> cl : classes) {
if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(systemEnv);
return (Map<String, String>) obj;
}
}
throw new IllegalStateException();
public void propertySourceOrder() {
ConfigurableEnvironment env = new DefaultEnvironment();
List<PropertySource<?>> sources = env.getPropertySources().asList();
assertThat(sources.size(), is(2));
assertThat(sources.get(0).getName(), equalTo(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME));
assertThat(sources.get(1).getName(), equalTo(DefaultEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME));
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2002-2010 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
/**
* Test that {@link Environment#getValue} performs late-resolution of property
* values i.e., does not eagerly resolve and cache only at construction time.
*
* @see EnvironmentPropertyResolutionSearchTests
* @author Chris Beams
* @since 3.1
*/
public class EnvironmentPropertyResolutionLateBindingTests {
@Test
public void replaceExistingKeyPostConstruction() {
String key = "foo";
String value1 = "bar";
String value2 = "biz";
System.setProperty(key, value1); // before construction
DefaultEnvironment env = new DefaultEnvironment();
assertThat(env.getProperty(key), equalTo(value1));
System.setProperty(key, value2); // after construction and first resolution
assertThat(env.getProperty(key), equalTo(value2));
System.clearProperty(key); // clean up
}
@Test
public void addNewKeyPostConstruction() {
DefaultEnvironment env = new DefaultEnvironment();
assertThat(env.getProperty("foo"), equalTo(null));
System.setProperty("foo", "42");
assertThat(env.getProperty("foo"), equalTo("42"));
System.clearProperty("foo"); // clean up
}
}

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2002-2010 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.junit.Test;
/**
* Unit tests for {@link DefaultEnvironment} proving that it (a) searches
* standard property sources (b) in the correct order.
*
* @see AbstractEnvironment#getProperty(String)
* @author Chris Beams
* @since 3.1
*/
public class EnvironmentPropertyResolutionSearchTests {
@Test @SuppressWarnings({ "unchecked", "serial", "rawtypes" })
public void propertySourcesHaveLIFOSearchOrder() {
ConfigurableEnvironment env = new AbstractEnvironment() { };
env.addPropertySource("ps1", new HashMap() {{ put("pName", "ps1Value"); }});
assertThat(env.getProperty("pName"), equalTo("ps1Value"));
env.addPropertySource("ps2", new HashMap() {{ put("pName", "ps2Value"); }});
assertThat(env.getProperty("pName"), equalTo("ps2Value"));
env.addPropertySource("ps3", new HashMap() {{ put("pName", "ps3Value"); }});
assertThat(env.getProperty("pName"), equalTo("ps3Value"));
}
@Test
public void resolveFromDefaultPropertySources() throws Exception {
String key = "x";
String localPropsValue = "local";
String sysPropsValue = "sys";
String envVarsValue = "env";
Map<String, String> systemEnvironment = getModifiableSystemEnvironment();
Properties systemProperties = System.getProperties();
Properties localProperties = new Properties();
DefaultEnvironment env = new DefaultEnvironment();
env.addPropertySource("localProperties", localProperties);
// set all properties
systemEnvironment.put(key, envVarsValue);
systemProperties.setProperty(key, sysPropsValue);
localProperties.setProperty(key, localPropsValue);
// local properties should have highest resolution precedence
assertThat(env.getProperty(key), equalTo(localPropsValue));
// system properties should be next in line
localProperties.remove(key);
assertThat(env.getProperty(key), equalTo(sysPropsValue));
// system environment variables should be final fallback
systemProperties.remove(key);
assertThat(env.getProperty(key), equalTo(envVarsValue));
// with no propertysource containing the key in question, should return null
systemEnvironment.remove(key);
assertThat(env.getProperty(key), equalTo(null));
}
@SuppressWarnings("unchecked")
private static Map<String, String> getModifiableSystemEnvironment() throws Exception {
Class<?>[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for (Class<?> cl : classes) {
if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
return (Map<String, String>) obj;
}
}
throw new IllegalStateException();
}
}

View File

@@ -0,0 +1,288 @@
/*
* Copyright 2002-2010 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 static java.lang.String.format;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.matchers.JUnitMatchers.hasItem;
import static org.junit.matchers.JUnitMatchers.hasItems;
import static org.springframework.core.env.AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME;
import static org.springframework.core.env.AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME;
import java.lang.reflect.Field;
import java.security.AccessControlException;
import java.security.Permission;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import org.junit.Test;
import org.springframework.mock.env.MockPropertySource;
/**
* Unit tests for {@link DefaultEnvironment}.
*
* @author Chris Beams
*/
public class EnvironmentTests {
private static final String ALLOWED_PROPERTY_NAME = "theanswer";
private static final String ALLOWED_PROPERTY_VALUE = "42";
private static final String DISALLOWED_PROPERTY_NAME = "verboten";
private static final String DISALLOWED_PROPERTY_VALUE = "secret";
private static final String STRING_PROPERTY_NAME = "stringPropName";
private static final String STRING_PROPERTY_VALUE = "stringPropValue";
private static final Object NON_STRING_PROPERTY_NAME = new Object();
private static final Object NON_STRING_PROPERTY_VALUE = new Object();
private ConfigurableEnvironment environment = new DefaultEnvironment();
@Test
public void activeProfiles() {
assertThat(environment.getActiveProfiles().length, is(0));
environment.setActiveProfiles("local", "embedded");
String[] activeProfiles = environment.getActiveProfiles();
assertThat(Arrays.asList(activeProfiles), hasItems("local", "embedded"));
assertThat(activeProfiles.length, is(2));
}
@Test
public void getActiveProfiles_systemPropertiesEmpty() {
assertThat(environment.getActiveProfiles().length, is(0));
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "");
assertThat(environment.getActiveProfiles().length, is(0));
System.getProperties().remove(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Test
public void getActiveProfiles_fromSystemProperties() {
assertThat(environment.getActiveProfiles().length, is(0));
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "foo");
assertThat(Arrays.asList(environment.getActiveProfiles()), hasItem("foo"));
System.getProperties().remove(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Test
public void getActiveProfiles_fromSystemProperties_withMultipleProfiles() {
assertThat(environment.getActiveProfiles().length, is(0));
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "foo,bar");
assertThat(Arrays.asList(environment.getActiveProfiles()), hasItems("foo", "bar"));
System.getProperties().remove(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Test
public void getActiveProfiles_fromSystemProperties_withMulitpleProfiles_withWhitespace() {
assertThat(environment.getActiveProfiles().length, is(0));
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, " bar , baz "); // notice whitespace
assertThat(Arrays.asList(environment.getActiveProfiles()), hasItems("bar", "baz"));
System.getProperties().remove(ACTIVE_PROFILES_PROPERTY_NAME);
}
@Test
public void getDefaultProfiles() {
assertThat(environment.getDefaultProfiles().length, is(0));
environment.getPropertySources().addFirst(new MockPropertySource().withProperty(DEFAULT_PROFILES_PROPERTY_NAME, "pd1"));
assertThat(environment.getDefaultProfiles().length, is(1));
assertThat(Arrays.asList(environment.getDefaultProfiles()), hasItem("pd1"));
}
@Test
public void setDefaultProfiles() {
environment.setDefaultProfiles();
assertThat(environment.getDefaultProfiles().length, is(0));
environment.setDefaultProfiles("pd1");
assertThat(Arrays.asList(environment.getDefaultProfiles()), hasItem("pd1"));
environment.setDefaultProfiles("pd2", "pd3");
assertThat(Arrays.asList(environment.getDefaultProfiles()), not(hasItem("pd1")));
assertThat(Arrays.asList(environment.getDefaultProfiles()), hasItems("pd2", "pd3"));
}
@Test(expected=IllegalArgumentException.class)
public void acceptsProfiles_mustSpecifyAtLeastOne() {
environment.acceptsProfiles();
}
@Test
public void acceptsProfiles_activeProfileSetProgrammatically() {
assertThat(environment.acceptsProfiles("p1", "p2"), is(false));
environment.setActiveProfiles("p1");
assertThat(environment.acceptsProfiles("p1", "p2"), is(true));
environment.setActiveProfiles("p2");
assertThat(environment.acceptsProfiles("p1", "p2"), is(true));
environment.setActiveProfiles("p1", "p2");
assertThat(environment.acceptsProfiles("p1", "p2"), is(true));
}
@Test
public void acceptsProfiles_activeProfileSetViaProperty() {
assertThat(environment.acceptsProfiles("p1"), is(false));
environment.getPropertySources().addFirst(new MockPropertySource().withProperty(ACTIVE_PROFILES_PROPERTY_NAME, "p1"));
assertThat(environment.acceptsProfiles("p1"), is(true));
}
@Test
public void acceptsProfiles_defaultProfile() {
assertThat(environment.acceptsProfiles("pd"), is(false));
environment.setDefaultProfiles("pd");
assertThat(environment.acceptsProfiles("pd"), is(true));
environment.setActiveProfiles("p1");
assertThat(environment.acceptsProfiles("pd"), is(false));
assertThat(environment.acceptsProfiles("p1"), is(true));
}
@Test
public void getSystemProperties_withAndWithoutSecurityManager() {
System.setProperty(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
System.setProperty(DISALLOWED_PROPERTY_NAME, DISALLOWED_PROPERTY_VALUE);
System.getProperties().put(STRING_PROPERTY_NAME, NON_STRING_PROPERTY_VALUE);
System.getProperties().put(NON_STRING_PROPERTY_NAME, STRING_PROPERTY_VALUE);
{
Map<?, ?> systemProperties = environment.getSystemProperties();
assertThat(systemProperties, notNullValue());
assertSame(systemProperties, System.getProperties());
assertThat(systemProperties.get(ALLOWED_PROPERTY_NAME), equalTo((Object)ALLOWED_PROPERTY_VALUE));
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME), equalTo((Object)DISALLOWED_PROPERTY_VALUE));
// non-string keys and values work fine... until the security manager is introduced below
assertThat(systemProperties.get(STRING_PROPERTY_NAME), equalTo(NON_STRING_PROPERTY_VALUE));
assertThat(systemProperties.get(NON_STRING_PROPERTY_NAME), equalTo((Object)STRING_PROPERTY_VALUE));
}
SecurityManager oldSecurityManager = System.getSecurityManager();
SecurityManager securityManager = new SecurityManager() {
@Override
public void checkPropertiesAccess() {
// see http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperties()
throw new AccessControlException("Accessing the system properties is disallowed");
}
@Override
public void checkPropertyAccess(String key) {
// see http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperty(java.lang.String)
if (DISALLOWED_PROPERTY_NAME.equals(key)) {
throw new AccessControlException(
format("Accessing the system property [%s] is disallowed", DISALLOWED_PROPERTY_NAME));
}
}
@Override
public void checkPermission(Permission perm) {
// allow everything else
}
};
System.setSecurityManager(securityManager);
{
Map<?, ?> systemProperties = environment.getSystemProperties();
assertThat(systemProperties, notNullValue());
assertThat(systemProperties, instanceOf(ReadOnlySystemAttributesMap.class));
assertThat((String)systemProperties.get(ALLOWED_PROPERTY_NAME), equalTo(ALLOWED_PROPERTY_VALUE));
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME), equalTo(null));
// nothing we can do here in terms of warning the user that there was
// actually a (non-string) value available. By this point, we only
// have access to calling System.getProperty(), which itself returns null
// if the value is non-string. So we're stuck with returning a potentially
// misleading null.
assertThat(systemProperties.get(STRING_PROPERTY_NAME), nullValue());
// in the case of a non-string *key*, however, we can do better. Alert
// the user that under these very special conditions (non-object key +
// SecurityManager that disallows access to system properties), they
// cannot do what they're attempting.
try {
systemProperties.get(NON_STRING_PROPERTY_NAME);
fail("Expected IllegalArgumentException when searching with non-string key against ReadOnlySystemAttributesMap");
} catch (IllegalArgumentException ex) {
// expected
}
}
System.setSecurityManager(oldSecurityManager);
System.clearProperty(ALLOWED_PROPERTY_NAME);
System.clearProperty(DISALLOWED_PROPERTY_NAME);
System.getProperties().remove(STRING_PROPERTY_NAME);
System.getProperties().remove(NON_STRING_PROPERTY_NAME);
}
@Test
public void getSystemEnvironment_withAndWithoutSecurityManager() throws Exception {
getModifiableSystemEnvironment().put(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
getModifiableSystemEnvironment().put(DISALLOWED_PROPERTY_NAME, DISALLOWED_PROPERTY_VALUE);
{
Map<String, String> systemEnvironment = environment.getSystemEnvironment();
assertThat(systemEnvironment, notNullValue());
assertSame(systemEnvironment, System.getenv());
}
SecurityManager oldSecurityManager = System.getSecurityManager();
SecurityManager securityManager = new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
//see http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getenv()
if ("getenv.*".equals(perm.getName())) {
throw new AccessControlException("Accessing the system environment is disallowed");
}
//see http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getenv(java.lang.String)
if (("getenv."+DISALLOWED_PROPERTY_NAME).equals(perm.getName())) {
throw new AccessControlException(
format("Accessing the system environment variable [%s] is disallowed", DISALLOWED_PROPERTY_NAME));
}
}
};
System.setSecurityManager(securityManager);
{
Map<String, String> systemEnvironment = environment.getSystemEnvironment();
assertThat(systemEnvironment, notNullValue());
assertThat(systemEnvironment, instanceOf(ReadOnlySystemAttributesMap.class));
assertThat(systemEnvironment.get(ALLOWED_PROPERTY_NAME), equalTo(ALLOWED_PROPERTY_VALUE));
assertThat(systemEnvironment.get(DISALLOWED_PROPERTY_NAME), nullValue());
}
System.setSecurityManager(oldSecurityManager);
getModifiableSystemEnvironment().remove(ALLOWED_PROPERTY_NAME);
getModifiableSystemEnvironment().remove(DISALLOWED_PROPERTY_NAME);
}
// TODO SPR-7508: duplicated from EnvironmentPropertyResolutionSearchTests
@SuppressWarnings("unchecked")
private static Map<String, String> getModifiableSystemEnvironment() throws Exception {
Class<?>[] classes = Collections.class.getDeclaredClasses();
Map<String, String> systemEnv = System.getenv();
for (Class<?> cl : classes) {
if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(systemEnv);
return (Map<String, String>) obj;
}
}
throw new IllegalStateException();
}
}

View File

@@ -0,0 +1,282 @@
/*
* Copyright 2002-2010 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.env.MockPropertySource;
/**
* Unit tests for {@link PropertyResolver}.
*
* @author Chris Beams
* @since 3.1
* @see PropertySourcesPropertyResolver
*/
public class PropertyResolverTests {
private Properties testProperties;
private MutablePropertySources propertySources;
private ConfigurablePropertyResolver propertyResolver;
@Before
public void setUp() {
propertySources = new MutablePropertySources();
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
testProperties = new Properties();
propertySources.addFirst(new PropertiesPropertySource("testProperties", testProperties));
}
@Test
public void containsProperty() {
assertThat(propertyResolver.containsProperty("foo"), is(false));
testProperties.put("foo", "bar");
assertThat(propertyResolver.containsProperty("foo"), is(true));
}
@Test
public void getProperty() {
assertThat(propertyResolver.getProperty("foo"), nullValue());
testProperties.put("foo", "bar");
assertThat(propertyResolver.getProperty("foo"), is("bar"));
}
@Test
public void getProperty_propertySourceSearchOrderIsFIFO() {
MutablePropertySources sources = new MutablePropertySources();
PropertyResolver resolver = new PropertySourcesPropertyResolver(sources);
sources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value"));
assertThat(resolver.getProperty("pName"), equalTo("ps1Value"));
sources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value"));
assertThat(resolver.getProperty("pName"), equalTo("ps2Value"));
sources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value"));
assertThat(resolver.getProperty("pName"), equalTo("ps3Value"));
}
@Test
public void getProperty_withExplicitNullValue() {
// java.util.Properties does not allow null values (because Hashtable does not)
Map<String, String> nullableProperties = new HashMap<String, String>();
propertySources.addLast(new MapPropertySource("nullableProperties", nullableProperties));
nullableProperties.put("foo", null);
assertThat(propertyResolver.getProperty("foo"), nullValue());
}
@Test
public void getProperty_withStringArrayConversion() {
testProperties.put("foo", "bar,baz");
assertThat(propertyResolver.getProperty("foo", String[].class), equalTo(new String[] { "bar", "baz" }));
}
@Test
public void getProperty_withNonConvertibleTargetType() {
testProperties.put("foo", "bar");
class TestType { }
try {
propertyResolver.getProperty("foo", TestType.class);
fail("Expected IllegalArgumentException due to non-convertible types");
} catch (IllegalArgumentException ex) {
// expected
}
}
@Test
public void getProperty_doesNotCache_replaceExistingKeyPostConstruction() {
String key = "foo";
String value1 = "bar";
String value2 = "biz";
HashMap<String, String> map = new HashMap<String, String>();
map.put(key, value1); // before construction
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MapPropertySource("testProperties", map));
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(propertyResolver.getProperty(key), equalTo(value1));
map.put(key, value2); // after construction and first resolution
assertThat(propertyResolver.getProperty(key), equalTo(value2));
}
@Test
public void getProperty_doesNotCache_addNewKeyPostConstruction() {
HashMap<String, String> map = new HashMap<String, String>();
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MapPropertySource("testProperties", map));
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(propertyResolver.getProperty("foo"), equalTo(null));
map.put("foo", "42");
assertThat(propertyResolver.getProperty("foo"), equalTo("42"));
}
@Test
public void getPropertySources_replacePropertySource() {
propertySources = new MutablePropertySources();
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
propertySources.addLast(new MockPropertySource("local").withProperty("foo", "localValue"));
propertySources.addLast(new MockPropertySource("system").withProperty("foo", "systemValue"));
// 'local' was added first so has precedence
assertThat(propertyResolver.getProperty("foo"), equalTo("localValue"));
// replace 'local' with new property source
propertySources.replace("local", new MockPropertySource("new").withProperty("foo", "newValue"));
// 'system' now has precedence
assertThat(propertyResolver.getProperty("foo"), equalTo("newValue"));
assertThat(propertySources.size(), is(2));
}
@Test
public void getRequiredProperty() {
testProperties.put("exists", "xyz");
assertThat(propertyResolver.getRequiredProperty("exists"), is("xyz"));
try {
propertyResolver.getRequiredProperty("bogus");
fail("expected IllegalStateException");
} catch (IllegalStateException ex) {
// expected
}
}
@Test
public void getRequiredProperty_withStringArrayConversion() {
testProperties.put("exists", "abc,123");
assertThat(propertyResolver.getRequiredProperty("exists", String[].class), equalTo(new String[] { "abc", "123" }));
try {
propertyResolver.getRequiredProperty("bogus", String[].class);
fail("expected IllegalStateException");
} catch (IllegalStateException ex) {
// expected
}
}
@Test
public void asProperties() {
propertySources = new MutablePropertySources();
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(propertyResolver.asProperties(), notNullValue());
propertySources.addLast(new MockPropertySource("highestPrecedence").withProperty("common", "highCommon").withProperty("highKey", "highVal"));
propertySources.addLast(new MockPropertySource("middlePrecedence").withProperty("common", "midCommon").withProperty("midKey", "midVal"));
propertySources.addLast(new MockPropertySource("lowestPrecedence").withProperty("common", "lowCommon").withProperty("lowKey", "lowVal"));
Properties props = propertyResolver.asProperties();
assertThat(props.getProperty("common"), is("highCommon"));
assertThat(props.getProperty("lowKey"), is("lowVal"));
assertThat(props.getProperty("midKey"), is("midVal"));
assertThat(props.getProperty("highKey"), is("highVal"));
assertThat(props.size(), is(4));
}
@Test
public void asProperties_withMixedPropertySourceTypes() {
class Foo { }
class FooPropertySource extends PropertySource<Foo> {
public FooPropertySource() { super("fooProperties", new Foo()); }
public String[] getPropertyNames() { return new String[] {"pName"}; }
public String getProperty(String key) { return "fooValue"; }
}
propertySources = new MutablePropertySources();
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(propertyResolver.asProperties(), notNullValue());
propertySources.addLast(new MockPropertySource());
propertySources.addLast(new FooPropertySource());
Properties props = propertyResolver.asProperties();
assertThat(props.getProperty("pName"), is("fooValue"));
assertThat(props.size(), is(1));
}
@Test
public void resolvePlaceholders() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key}"), equalTo("Replace this value"));
}
@Test
public void resolvePlaceholders_withUnresolvable() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"),
equalTo("Replace this value plus ${unknown}"));
}
@Test
public void resolvePlaceholders_withDefault() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"),
equalTo("Replace this value plus defaultValue"));
}
@Test(expected=IllegalArgumentException.class)
public void resolvePlaceholders_withNullInput() {
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolvePlaceholders(null);
}
@Test
public void resolveRequiredPlaceholders() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key}"), equalTo("Replace this value"));
}
@Test(expected=IllegalArgumentException.class)
public void resolveRequiredPlaceholders_withUnresolvable() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}");
}
@Test
public void resolveRequiredPlaceholders_withDefault() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"),
equalTo("Replace this value plus defaultValue"));
}
@Test(expected=IllegalArgumentException.class)
public void resolveRequiredPlaceholders_withNullInput() {
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null);
}
}

View File

@@ -20,8 +20,9 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -66,7 +67,7 @@ public class PropertySourceTests {
PropertySource<?> ps1 = new MapPropertySource("ps1", map1);
ps1.getSource();
LinkedList<PropertySource<?>> propertySources = new LinkedList<PropertySource<?>>();
List<PropertySource<?>> propertySources = new ArrayList<PropertySource<?>>();
assertThat(propertySources.add(ps1), equalTo(true));
assertThat(propertySources.contains(ps1), is(true));
assertThat(propertySources.contains(PropertySource.named("ps1")), is(true));

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2002-2010 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.mock.env.MockPropertySource;
public class PropertySourcesTests {
@Test
public void test() {
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(new MockPropertySource("b").withProperty("p1", "bValue"));
sources.addLast(new MockPropertySource("d").withProperty("p1", "dValue"));
sources.addLast(new MockPropertySource("f").withProperty("p1", "fValue"));
assertThat(sources.size(), equalTo(3));
assertThat(sources.contains("a"), is(false));
assertThat(sources.contains("b"), is(true));
assertThat(sources.contains("c"), is(false));
assertThat(sources.contains("d"), is(true));
assertThat(sources.contains("e"), is(false));
assertThat(sources.contains("f"), is(true));
assertThat(sources.contains("g"), is(false));
assertThat(sources.get("b"), not(nullValue()));
assertThat(sources.get("b").getProperty("p1"), equalTo("bValue"));
assertThat(sources.get("d"), not(nullValue()));
assertThat(sources.get("d").getProperty("p1"), equalTo("dValue"));
sources.addBefore("b", new MockPropertySource("a"));
sources.addAfter("b", new MockPropertySource("c"));
assertThat(sources.size(), equalTo(5));
assertThat(sources.asList().indexOf(PropertySource.named("a")), is(0));
assertThat(sources.asList().indexOf(PropertySource.named("b")), is(1));
assertThat(sources.asList().indexOf(PropertySource.named("c")), is(2));
assertThat(sources.asList().indexOf(PropertySource.named("d")), is(3));
assertThat(sources.asList().indexOf(PropertySource.named("f")), is(4));
sources.addBefore("f", new MockPropertySource("e"));
sources.addAfter("f", new MockPropertySource("g"));
assertThat(sources.size(), equalTo(7));
assertThat(sources.asList().indexOf(PropertySource.named("a")), is(0));
assertThat(sources.asList().indexOf(PropertySource.named("b")), is(1));
assertThat(sources.asList().indexOf(PropertySource.named("c")), is(2));
assertThat(sources.asList().indexOf(PropertySource.named("d")), is(3));
assertThat(sources.asList().indexOf(PropertySource.named("e")), is(4));
assertThat(sources.asList().indexOf(PropertySource.named("f")), is(5));
assertThat(sources.asList().indexOf(PropertySource.named("g")), is(6));
sources.addLast(new MockPropertySource("a"));
assertThat(sources.size(), equalTo(7));
assertThat(sources.asList().indexOf(PropertySource.named("b")), is(0));
assertThat(sources.asList().indexOf(PropertySource.named("c")), is(1));
assertThat(sources.asList().indexOf(PropertySource.named("d")), is(2));
assertThat(sources.asList().indexOf(PropertySource.named("e")), is(3));
assertThat(sources.asList().indexOf(PropertySource.named("f")), is(4));
assertThat(sources.asList().indexOf(PropertySource.named("g")), is(5));
assertThat(sources.asList().indexOf(PropertySource.named("a")), is(6));
sources.addFirst(new MockPropertySource("a"));
assertThat(sources.size(), equalTo(7));
assertThat(sources.asList().indexOf(PropertySource.named("a")), is(0));
assertThat(sources.asList().indexOf(PropertySource.named("b")), is(1));
assertThat(sources.asList().indexOf(PropertySource.named("c")), is(2));
assertThat(sources.asList().indexOf(PropertySource.named("d")), is(3));
assertThat(sources.asList().indexOf(PropertySource.named("e")), is(4));
assertThat(sources.asList().indexOf(PropertySource.named("f")), is(5));
assertThat(sources.asList().indexOf(PropertySource.named("g")), is(6));
assertEquals(sources.remove("a"), PropertySource.named("a"));
assertThat(sources.size(), equalTo(6));
assertThat(sources.contains("a"), is(false));
assertEquals(sources.remove("a"), null);
assertThat(sources.size(), equalTo(6));
String bogusPS = "bogus";
try {
sources.addAfter(bogusPS, new MockPropertySource("h"));
fail("expected non-existent PropertySource exception");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),
equalTo(String.format(MutablePropertySources.NON_EXISTENT_PROPERTY_SOURCE_MESSAGE, bogusPS)));
}
sources.addFirst(new MockPropertySource("a"));
assertThat(sources.size(), equalTo(7));
assertThat(sources.asList().indexOf(PropertySource.named("a")), is(0));
assertThat(sources.asList().indexOf(PropertySource.named("b")), is(1));
assertThat(sources.asList().indexOf(PropertySource.named("c")), is(2));
sources.replace("a", new MockPropertySource("a-replaced"));
assertThat(sources.size(), equalTo(7));
assertThat(sources.asList().indexOf(PropertySource.named("a-replaced")), is(0));
assertThat(sources.asList().indexOf(PropertySource.named("b")), is(1));
assertThat(sources.asList().indexOf(PropertySource.named("c")), is(2));
sources.replace("a-replaced", new MockPropertySource("a"));
try {
sources.replace(bogusPS, new MockPropertySource("bogus-replaced"));
fail("expected non-existent PropertySource exception");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),
equalTo(String.format(MutablePropertySources.NON_EXISTENT_PROPERTY_SOURCE_MESSAGE, bogusPS)));
}
try {
sources.addBefore("b", new MockPropertySource("b"));
fail("expected exception");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),
equalTo(String.format(MutablePropertySources.ILLEGAL_RELATIVE_ADDITION_MESSAGE, "b")));
}
try {
sources.addAfter("b", new MockPropertySource("b"));
fail("expected exception");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),
equalTo(String.format(MutablePropertySources.ILLEGAL_RELATIVE_ADDITION_MESSAGE, "b")));
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.mock.env;
import java.util.Properties;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
/**
* Simple {@link PropertySource} implementation for use in testing. Accepts
* a user-provided {@link Properties} object, or if omitted during construction,
* the implementation will initialize its own.
*
* The {@link #setProperty} and {@link #withProperty} methods are exposed for
* convenience, for example:
* <pre>
* {@code
* PropertySource<?> source = new MockPropertySource().withProperty("foo", "bar");
* }
* </pre>
*
* @author Chris Beams
* @since 3.1
* @see MockEnvironment
*/
public class MockPropertySource extends PropertiesPropertySource {
/**
* {@value} is the default name for {@link MockPropertySource} instances not
* otherwise given an explicit name.
* @see #MockPropertySource()
* @see #MockPropertySource(String)
*/
public static final String MOCK_PROPERTIES_PROPERTY_SOURCE_NAME = "mockProperties";
/**
* Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME}
* that will maintain its own internal {@link Properties} instance.
*/
public MockPropertySource() {
this(new Properties());
}
/**
* Create a new {@code MockPropertySource} with the given name that will
* maintain its own internal {@link Properties} instance.
* @param name the {@linkplain #getName() name} of the property source
*/
public MockPropertySource(String name) {
this(name, new Properties());
}
/**
* Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME}
* and backed by the given {@link Properties} object.
* @param properties the properties to use
*/
public MockPropertySource(Properties properties) {
this(MOCK_PROPERTIES_PROPERTY_SOURCE_NAME, properties);
}
/**
* Create a new {@code MockPropertySource} with with the given name and backed by the given
* {@link Properties} object
* @param name the {@linkplain #getName() name} of the property source
* @param properties the properties to use
*/
public MockPropertySource(String name, Properties properties) {
super(name, properties);
}
/**
* Set the given property on the underlying {@link Properties} object.
*/
public void setProperty(String key, String value) {
this.source.put(key, value);
}
/**
* Convenient synonym for {@link #setProperty} that returns the current instance.
* Useful for method chaining and fluent-style use.
* @return this {@link MockPropertySource} instance
*/
public MockPropertySource withProperty(String key, String value) {
this.setProperty(key, value);
return this;
}
}