diff --git a/.gitignore b/.gitignore index 55517f74172..6b6a4200bec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,17 @@ -target +*.java.hsp +*.sonarj +*.sw* +.DS_Store +.settings +.springBeans bin +build.sh integration-repo ivy-cache jmx.log -.springBeans -.settings -.DS_Store -*.sw* -org.springframework.test/test-output/ +org.springframework.jdbc/derby.log org.springframework.spring-parent/.classpath org.springframework.spring-parent/.project -build.sh -org.springframework.jdbc/derby.log +org.springframework.test/test-output/ spring-build +target diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index c49b9065056..4e00489b1c9 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -152,7 +152,7 @@ public interface BeanFactory { /** * Return the bean instance that uniquely matches the given object type, if any. * @param requiredType type the bean must match; can be an interface or superclass. - * {@literal null} is disallowed. + * {@code null} is disallowed. *

This method goes into {@link ListableBeanFactory} by-type lookup territory * but may also be translated into a conventional by-name lookup based on the name * of the given type. For more extensive retrieval operations across sets of beans, diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java index 7f1f73d1cbd..2bdc1e5e5b0 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java @@ -432,7 +432,6 @@ public class SingletonBeanFactoryLocator implements BeanFactoryLocator { protected BeanFactory createDefinition(String resourceLocation, String factoryKey) { DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); - // TODO SPR-7508: consider whether to allow for setEnvironment() here (where would it come from?) ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); try { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractPropertyPlaceholderConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractPropertyPlaceholderConfigurer.java index c3fd69f3f6d..b03e529d4c9 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractPropertyPlaceholderConfigurer.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractPropertyPlaceholderConfigurer.java @@ -29,26 +29,87 @@ import org.springframework.util.StringValueResolver; /** - * TODO SPR-7508: document. + * Abstract base class for property resource configurers that resolve placeholders + * in bean definition property values. Implementations pull values from a + * properties file or other {@linkplain org.springframework.core.env.PropertySource + * property source} into bean definitions. + * + *

The default placeholder syntax follows the Ant / Log4J / JSP EL style: + * + *

${...}
+ * + * Example XML bean definition: + * + *
{@code
+ *
+ *    
+ *    
+ *
+ *}
+ * + * Example properties file: + * + *
 driver=com.mysql.jdbc.Driver
+ * dbname=mysql:mydb
+ * + * Annotated bean definitions may take advantage of property replacement using + * the {@link org.springframework.beans.factory.annotation.Value @Value} annotation: + * + *
@Value("${person.age}")
+ * + * Implementations check simple property values, lists, maps, props, and bean names + * in bean references. Furthermore, placeholder values can also cross-reference + * other placeholders, like: + * + *
rootPath=myrootdir
+ *subPath=${rootPath}/subdir
+ * + * In contrast to {@link PropertyOverrideConfigurer}, subclasses of this type allow + * filling in of explicit placeholders in bean definitions. + * + *

If a configurer cannot resolve a placeholder, a {@link BeanDefinitionStoreException} + * will be thrown. If you want to check against multiple properties files, specify multiple + * resources via the {@link #setLocations locations} property. You can also define multiple + * configurers, each with its own placeholder syntax. Use {@link + * #ignoreUnresolvablePlaceholders} to intentionally suppress throwing an exception if a + * placeholder cannot be resolved. + * + *

Default property values can be defined globally for each configurer instance + * via the {@link #setProperties properties} property, or on a property-by-property basis + * using the default value separator which is {@code ":"} by default and + * customizable via {@link #setValueSeparator(String)}. + * + *

Example XML property with default value: + * + *

{@code
+ *  
+ *}
* * @author Chris Beams * @author Juergen Hoeller * @since 3.1 + * @see PropertyPlaceholderConfigurer + * @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer */ public abstract class AbstractPropertyPlaceholderConfigurer extends PropertyResourceConfigurer implements BeanNameAware, BeanFactoryAware { - /** Default placeholder prefix: "${" */ + /** Default placeholder prefix: {@value} */ public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; - /** Default placeholder suffix: "}" */ + + /** Default placeholder suffix: {@value} */ public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; - /** Default value separator: ":" */ + + /** Default value separator: {@value} */ public static final String DEFAULT_VALUE_SEPARATOR = ":"; + /** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX} */ protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX; + /** Defaults to {@value #DEFAULT_PLACEHOLDER_SUFFIX} */ protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX; + /** Defaults to {@value #DEFAULT_VALUE_SEPARATOR} */ protected String valueSeparator = DEFAULT_VALUE_SEPARATOR; protected boolean ignoreUnresolvablePlaceholders = false; @@ -60,12 +121,14 @@ public abstract class AbstractPropertyPlaceholderConfigurer extends PropertyReso private BeanFactory beanFactory; + /** + * Return the {@code PlaceholderResolver} for this configurer. + */ protected abstract PlaceholderResolver getPlaceholderResolver(Properties props); /** * Set the prefix that a placeholder string starts with. - * The default is "${". - * @see #DEFAULT_PLACEHOLDER_PREFIX + * The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}. */ public void setPlaceholderPrefix(String placeholderPrefix) { this.placeholderPrefix = placeholderPrefix; @@ -73,8 +136,7 @@ public abstract class AbstractPropertyPlaceholderConfigurer extends PropertyReso /** * Set the suffix that a placeholder string ends with. - * The default is "}". - * @see #DEFAULT_PLACEHOLDER_SUFFIX + * The default is {@value #DEFAULT_PLACEHOLDER_SUFFIX}. */ public void setPlaceholderSuffix(String placeholderSuffix) { this.placeholderSuffix = placeholderSuffix; @@ -82,22 +144,22 @@ public abstract class AbstractPropertyPlaceholderConfigurer extends PropertyReso /** * Specify the separating character between the placeholder variable - * and the associated default value, or null if no such + * and the associated default value, or {@code null} if no such * special character should be processed as a value separator. - * The default is ":". + * The default is {@value #DEFAULT_VALUE_SEPARATOR}. */ public void setValueSeparator(String valueSeparator) { this.valueSeparator = valueSeparator; } /** - * Set a value that should be treated as null when + * Set a value that should be treated as {@code null} when * resolved as a placeholder value: e.g. "" (empty String) or "null". *

Note that this will only apply to full property values, * not to parts of concatenated values. *

By default, no such null value is defined. This means that - * there is no way to express null as a property - * value unless you explictly map a corresponding value here. + * there is no way to express {@code null} as a property + * value unless you explicitly map a corresponding value here. */ public void setNullValue(String nullValue) { this.nullValue = nullValue; @@ -139,6 +201,10 @@ public abstract class AbstractPropertyPlaceholderConfigurer extends PropertyReso } + /** + * Visit each bean definition in the given bean factory and attempt to replace ${...} property + * placeholders with values from the given properties. + */ @Override protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java index 5274e9a08b5..b823d0f917a 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java @@ -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. @@ -25,69 +25,40 @@ import org.springframework.core.Constants; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; - /** - * A property resource configurer that resolves placeholders in bean property values of - * context definitions. It pulls values from a properties file into bean definitions. + * {@link AbstractPropertyPlaceholderConfigurer} subclass that resolves ${...} placeholders + * against {@link #setLocation local} {@link #setProperties properties} and/or system properties + * and environment variables. * - *

The default placeholder syntax follows the Ant / Log4J / JSP EL style: + *

As of Spring 3.1, {@link org.springframework.context.support.PropertySourcesPlaceholderConfigurer + * PropertySourcesPlaceholderConfigurer} should be used preferentially over this implementation; it is + * more flexible through taking advantage of the {@link org.springframework.core.env.Environment Environment} and + * {@link org.springframework.core.env.PropertySource PropertySource} mechanisms also made available in Spring 3.1. * - *

${...}
+ *

{@link PropertyPlaceholderConfigurer} is still appropriate for use when: + *

* - * Example XML context definition: - * - *
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- *   <property name="driverClassName"><value>${driver}</value></property>
- *   <property name="url"><value>jdbc:${dbname}</value></property>
- * </bean>
- * - * Example properties file: - * - *
driver=com.mysql.jdbc.Driver
- * dbname=mysql:mydb
- * - * PropertyPlaceholderConfigurer checks simple property values, lists, maps, - * props, and bean names in bean references. Furthermore, placeholder values can - * also cross-reference other placeholders, like: - * - *
rootPath=myrootdir
- * subPath=${rootPath}/subdir
- * - * In contrast to PropertyOverrideConfigurer, this configurer allows to fill in - * explicit placeholders in context definitions. Therefore, the original definition - * cannot specify any default values for such bean properties, and the placeholder - * properties file is supposed to contain an entry for each defined placeholder. - * - *

If a configurer cannot resolve a placeholder, a BeanDefinitionStoreException - * will be thrown. If you want to check against multiple properties files, specify - * multiple resources via the "locations" setting. You can also define multiple - * PropertyPlaceholderConfigurers, each with its own placeholder syntax. - * - *

Default property values can be defined via "properties", to make overriding - * definitions in properties files optional. A configurer will also check against - * system properties (e.g. "user.dir") if it cannot resolve a placeholder with any - * of the specified properties. This can be customized via "systemPropertiesMode". - * - *

Note that the context definition is aware of being incomplete; - * this is immediately obvious to users when looking at the XML definition file. - * Hence, placeholders have to be resolved; any desired defaults have to be - * defined as placeholder values as well (for example in a default properties file). - * - *

Property values can be converted after reading them in, through overriding - * the {@link #convertPropertyValue} method. For example, encrypted values can - * be detected and decrypted accordingly before processing them. + *

Prior to Spring 3.1, the {@code } namespace element + * registered an instance of {@code PropertyPlaceholderConfigurer}. It will still do so if + * using the {@code spring-beans-3.0.xsd} definition of the namespace. That is, you can preserve + * registration of {@code PropertyPlaceholderConfigurer} through the namespace, even if using Spring 3.1; + * simply do not update your {@code xsi:schemaLocation} and continue using the 3.0 XSD. * * @author Juergen Hoeller * @author Chris Beams * @since 02.10.2003 - * @see #setLocations - * @see #setProperties - * @see #setPlaceholderPrefix - * @see #setPlaceholderSuffix * @see #setSystemPropertiesModeName - * @see System#getProperty(String) - * @see #convertPropertyValue + * @see AbstractPropertyPlaceholderConfigurer * @see PropertyOverrideConfigurer + * @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer */ public class PropertyPlaceholderConfigurer extends AbstractPropertyPlaceholderConfigurer implements BeanNameAware, BeanFactoryAware { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyResourceConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyResourceConfigurer.java index ffcd35d9bc0..8da5a7772b1 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyResourceConfigurer.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyResourceConfigurer.java @@ -55,15 +55,24 @@ public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered + /** + * Set the order value of this object for sorting purposes. + * @see PriorityOrdered + */ public void setOrder(int order) { - this.order = order; + this.order = order; } public int getOrder() { - return this.order; + return this.order; } + /** + * {@linkplain #mergeProperties Merge}, {@linkplain #convertProperties convert} and + * {@linkplain #processProperties process} properties against the given bean factory. + * @throws BeanInitializationException if any properties cannot be loaded + */ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { Properties mergedProps = mergeProperties(); @@ -88,7 +97,7 @@ public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport * @see #processProperties */ protected void convertProperties(Properties props) { - Enumeration propertyNames = props.propertyNames(); + Enumeration propertyNames = props.propertyNames(); while (propertyNames.hasMoreElements()) { String propertyName = (String) propertyNames.nextElement(); String propertyValue = props.getProperty(propertyName); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java index 25fd17c84df..333757b75f9 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java @@ -39,6 +39,7 @@ import org.springframework.util.Assert; * and the class loader to use for loading bean classes. * * @author Juergen Hoeller + * @author Chris Beams * @since 11.12.2003 * @see BeanDefinitionReaderUtils */ @@ -140,16 +141,14 @@ public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable } /** - * TODO SPR-7508: document - * @param environment + * Set the Environment to use when reading bean definitions. Most often used + * for evaluating profile information to determine which bean definitions + * should be read and which should be omitted. */ public void setEnvironment(Environment environment) { this.environment = environment; } - /** - * TODO SPR-7508: document - */ public Environment getEnvironment() { return this.environment; } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 69701005050..6b540075340 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -70,7 +70,6 @@ import org.springframework.core.NamedThreadLocal; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.DefaultEnvironment; -import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -170,8 +169,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp private final ThreadLocal prototypesCurrentlyInCreation = new NamedThreadLocal("Prototype beans currently in creation"); - private ConfigurableEnvironment environment = new DefaultEnvironment(); - /** * Create a new AbstractBeanFactory. diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index b9b8b8caa4f..3e8784b1817 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -33,8 +33,6 @@ import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.DefaultEnvironment; import org.springframework.util.StringUtils; /** @@ -61,9 +59,6 @@ public class StaticListableBeanFactory implements ListableBeanFactory { /** Map from bean name to bean instance */ private final Map beans = new HashMap(); - /** TODO SPR-7508: document */ - private ConfigurableEnvironment environment = new DefaultEnvironment(); - /** * Add a new singleton bean. diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java index decc6cd7e5a..ec161a06843 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java @@ -16,9 +16,10 @@ package org.springframework.beans.factory.xml; -import org.w3c.dom.Document; - import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.core.env.Environment; + +import org.w3c.dom.Document; /** * SPI for parsing an XML document that contains Spring bean definitions. @@ -46,4 +47,11 @@ public interface BeanDefinitionDocumentReader { void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException; + /** + * Set the Environment to use when reading bean definitions. Used for evaluating + * profile information to determine whether a {@code } document/element should + * be included or omitted. + */ + void setEnvironment(Environment environment); + } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java index de9123c2e3f..7236aa61c0f 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java @@ -1476,7 +1476,7 @@ public class BeanDefinitionParserDelegate { /** * Determine whether the name of the supplied node is equal to the supplied name. *

The default implementation checks the supplied desired name against both - * {@link Node#getNodeName()) and {@link Node#getLocalName()}. + * {@link Node#getNodeName()} and {@link Node#getLocalName()}. *

Subclasses may override the default implementation to provide a different * mechanism for comparing node names. * @param node the node to compare diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java index ab79610932a..4d0b5c260fe 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java @@ -38,6 +38,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.util.Assert; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; @@ -88,18 +89,21 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume /** - * TODO SPR-7508: document - * @param environment + * {@inheritDoc} + *

Default value is {@code null}; property is required for parsing any + * {@code } element with a {@code profile} attribute present. + * @see #doRegisterBeanDefinitions */ public void setEnvironment(Environment environment) { this.environment = environment; } /** - * Parses bean definitions according to the "spring-beans" DTD. TODO SPR-7508 XSD + * {@inheritDoc} + *

This implementation parses bean definitions according to the "spring-beans" XSD + * (or DTD, historically). *

Opens a DOM Document; then initializes the default settings - * specified at <beans> level; then parses - * the contained bean definitions. + * specified at the {@code } level; then parses the contained bean definitions. */ public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; @@ -110,17 +114,24 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume doRegisterBeanDefinitions(root); } + /** + * Register each bean definition within the given root {@code } element. + * @throws IllegalStateException if {@code elements will cause recursion in this method. in + // any nested elements will cause recursion in this method. In // order to propagate and preserve default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, @@ -212,7 +223,7 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume } // Resolve system properties: e.g. "${user.dir}" - location = environment.resolveRequiredPlaceholders(location); + location = environment.getPropertyResolver().resolveRequiredPlaceholders(location); Set actualResources = new LinkedHashSet(4); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java index baf2ccc4e8e..d858fe4defa 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java @@ -487,12 +487,8 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { - // Read document based on new BeanDefinitionDocumentReader SPI. // TODO SPR-7508: polish - remove comment BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); - // TODO SPR-7508: remove ugly cast - if (documentReader instanceof DefaultBeanDefinitionDocumentReader) { - ((DefaultBeanDefinitionDocumentReader)documentReader).setEnvironment(this.getEnvironment()); - } + documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanFactory.java index 4abaa002a6f..077d76cdaeb 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 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. @@ -19,7 +19,6 @@ package org.springframework.beans.factory.xml; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.Resource; /** @@ -35,7 +34,8 @@ import org.springframework.core.io.Resource; *

This class registers each bean definition with the {@link DefaultListableBeanFactory} * superclass, and relies on the latter's implementation of the {@link BeanFactory} interface. * It supports singletons, prototypes, and references to either of these kinds of bean. - * See "spring-beans-2.0.dtd" for details on options and configuration style. // TODO SPR-7508 polish - s/dtd/xsd/ + * See {@code "spring-beans-3.x.xsd"} (or historically, {@code "spring-beans-2.0.dtd"}) for + * details on options and configuration style. * *

For advanced needs, consider using a {@link DefaultListableBeanFactory} with * an {@link XmlBeanDefinitionReader}. The latter allows for reading from multiple XML @@ -63,7 +63,7 @@ public class XmlBeanFactory extends DefaultListableBeanFactory { * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { - this(resource, (BeanFactory)null); + this(resource, null); } /** diff --git a/org.springframework.beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.1.xsd b/org.springframework.beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.1.xsd index e4837178674..ec75e95e074 100644 --- a/org.springframework.beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.1.xsd +++ b/org.springframework.beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.1.xsd @@ -36,8 +36,6 @@ There is also support for lists, sets, maps, and java.util.Properties as bean property types or constructor argument types. - - TODO:SPR-7508: Document use of nested beans elements. ]]> @@ -63,8 +61,11 @@ and other elements, typically the root element in the document. + Allows the definition of default values for all nested bean definitions. May itself + be nested for the purpose of defining a subset of beans with certain default values or + to be registered only when certain profile(s) are active. Any such nested element + must be declared as the last element in the document. ]]> @@ -81,16 +82,20 @@ element may be parsed. May be a comma-delimited + list in the case of multiple profiles. If one or more of the specified profiles are active at time + of parsing, the element will be parsed, and all of its elements registered, + <import> elements followed, etc. If none of the specified profiles are active at time of parsing, + then the entire element and its contents will be ignored. + + Profiles are activated in one of two ways: + Programmatic: + ConfigurableEnvironment#setActiveProfiles(String...) + ConfigurableEnvironment#setDefaultProfiles(String...) + + Properties (typically through -D system properties, environment variables, or servlet context init params): + spring.profile.active=p1,p2 + spring.profile.default=p1,p2 ]]> @@ -115,7 +120,7 @@ diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java index bbbd1eaacee..9e039550a74 100644 --- a/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java @@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.registerWithGeneratedName; @@ -33,13 +34,16 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import test.beans.TestBean; /** - * Unit tests for {@link EnvironmentAwarePropertyPlaceholderConfigurer}. + * Unit tests for {@link PropertyPlaceholderConfigurer}. * + * @see PropertySourcesPlaceholderConfigurerTests * @see PropertyResourceConfigurerTests * @author Chris Beams */ @@ -50,7 +54,7 @@ public class PropertyPlaceholderConfigurerTests { private static final String P1_SYSTEM_ENV_VAL = "p1SystemEnvVal"; private DefaultListableBeanFactory bf; - private AbstractPropertyPlaceholderConfigurer ppc; + private PropertyPlaceholderConfigurer ppc; private Properties ppcProperties; private AbstractBeanDefinition p1BeanDef; @@ -79,9 +83,19 @@ public class PropertyPlaceholderConfigurerTests { } - // ------------------------------------------------------------------------- - // Tests to ensure backward-compatibility for Environment refactoring - // ------------------------------------------------------------------------- + @Test + public void localPropertiesViaResource() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + + PropertyPlaceholderConfigurer pc = new PropertyPlaceholderConfigurer(); + Resource resource = new ClassPathResource("PropertyPlaceholderConfigurerTests.properties", this.getClass()); + pc.setLocation(resource); + pc.postProcessBeanFactory(bf); + } @Test public void resolveFromSystemProperties() { @@ -115,7 +129,6 @@ public class PropertyPlaceholderConfigurerTests { assertThat(bean.getName(), equalTo(P1_LOCAL_PROPS_VAL)); } - /* @Test public void setSystemSystemPropertiesMode_toOverride_andResolveFromSystemProperties() { registerWithGeneratedName(p1BeanDef, bf); @@ -145,7 +158,6 @@ public class PropertyPlaceholderConfigurerTests { TestBean bean = bf.getBean(TestBean.class); assertThat(bean.getName(), equalTo(P1_LOCAL_PROPS_VAL)); // has to resort to local props } - */ /** * Creates a scenario in which two PPCs are configured, each with different @@ -231,34 +243,6 @@ public class PropertyPlaceholderConfigurerTests { } - // ------------------------------------------------------------------------- - // Tests for functionality not possible prior to Environment refactoring - // ------------------------------------------------------------------------- - - /** - * Tests that properties against a BeanFactory's Environment are used by - * PropertyPlaceholderConfigurer during placeholder resolution. - @Test @SuppressWarnings({ "unchecked", "rawtypes", "serial" }) - public void replacePlaceholdersFromBeanFactoryEnvironmentPropertySources() { - System.setProperty("key1", "systemValue"); - - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.getEnvironment().addPropertySource("psCustom", new HashMap() {{ put("key1", "customValue"); }}); - bf.registerBeanDefinition("testBean", - rootBeanDefinition(TestBean.class).addPropertyValue("name", "${key1}").getBeanDefinition()); - - new PropertyPlaceholderConfigurer().postProcessBeanFactory(bf); - assertThat(bf.getBean(TestBean.class).getName(), is("customValue")); - - System.clearProperty("key1"); - } - */ - - - // ------------------------------------------------------------------------- - // Utilities - // ------------------------------------------------------------------------- - // TODO SPR-7508: duplicated from EnvironmentPropertyResolutionSearchTests @SuppressWarnings("unchecked") private static Map getModifiableSystemEnvironment() { diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.properties b/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.properties new file mode 100644 index 00000000000..b8f69781481 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.properties @@ -0,0 +1 @@ +my.name=foo \ No newline at end of file diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java index ca4d5afbefb..fa790bdd99c 100644 --- a/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java @@ -800,7 +800,7 @@ public final class PropertyResourceConfigurerTests { Preferences.systemRoot().node("mySystemPath/mypath").remove("myName"); } - /* TODO SPR-7508: uncomment after EnvironmentAwarePropertyPlaceholderConfigurer implementation + /* TODO SPR-7508: uncomment after PropertySourcesPlaceholderConfigurer implementation @Test public void testPreferencesPlaceholderConfigurerWithCustomPropertiesInEnvironment() { factory.registerBeanDefinition("tb", diff --git a/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java index 08fa69d06ab..fa57b64cb56 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * 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. @@ -96,12 +96,13 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life void setParent(ApplicationContext parent); /** - * TODO SPR-7508: document + * Return the Environment for this application context in configurable form. */ ConfigurableEnvironment getEnvironment(); /** - * TODO SPR-7508: document + * Set the {@code Environment} for this application context. + * @param environment the new environment */ void setEnvironment(ConfigurableEnvironment environment); diff --git a/org.springframework.context/src/main/java/org/springframework/context/EnvironmentAware.java b/org.springframework.context/src/main/java/org/springframework/context/EnvironmentAware.java index 7ec52896b4c..d71b5ee3043 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/EnvironmentAware.java +++ b/org.springframework.context/src/main/java/org/springframework/context/EnvironmentAware.java @@ -19,12 +19,17 @@ package org.springframework.context; import org.springframework.core.env.Environment; /** - * TODO SPR-7515: document + * Interface to be implemented by any bean that wishes to be notified + * of the {@link Environment} that it runs in. * * @author Chris Beams + * @since 3.1 */ public interface EnvironmentAware { + /** + * Set the {@code Environment} that this object runs in. + */ void setEnvironment(Environment environment); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java index 8aebaa07b96..aa8a2117fd3 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * 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. @@ -50,9 +50,9 @@ public class AnnotatedBeanDefinitionReader { /** - * Create a new AnnotatedBeanDefinitionReader for the given bean factory. - * @param registry the BeanFactory to load bean definitions into, - * in the form of a BeanDefinitionRegistry + * Create a new {@code AnnotatedBeanDefinitionReader} for the given bean factory. + * @param registry the {@code BeanFactory} to load bean definitions into, + * in the form of a {@code BeanDefinitionRegistry} */ public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) { this.registry = registry; @@ -68,8 +68,10 @@ public class AnnotatedBeanDefinitionReader { } /** - * Set the Environment to use when registering classes. + * Set the Environment to use when evaluating whether + * {@link Profile @Profile}-annotated component classes should be registered. *

The default is a {@link DefaultEnvironment}. + * @see #registerBean(Class, String, Class...) */ public void setEnvironment(Environment environment) { this.environment = environment; @@ -80,7 +82,8 @@ public class AnnotatedBeanDefinitionReader { *

The default is a {@link AnnotationBeanNameGenerator}. */ public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { - this.beanNameGenerator = (beanNameGenerator != null ? beanNameGenerator : new AnnotationBeanNameGenerator()); + this.beanNameGenerator = (beanNameGenerator != null ? + beanNameGenerator : new AnnotationBeanNameGenerator()); } /** @@ -88,7 +91,8 @@ public class AnnotatedBeanDefinitionReader { *

The default is an {@link AnnotationScopeMetadataResolver}. */ public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { - this.scopeMetadataResolver = (scopeMetadataResolver != null ? scopeMetadataResolver : new AnnotationScopeMetadataResolver()); + this.scopeMetadataResolver = (scopeMetadataResolver != null ? + scopeMetadataResolver : new AnnotationScopeMetadataResolver()); } @@ -110,10 +114,8 @@ public class AnnotatedBeanDefinitionReader { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); AnnotationMetadata metadata = abd.getMetadata(); - if (metadata.hasAnnotation(Profile.class.getName())) { - String[] specifiedProfiles = - (String[])metadata.getAnnotationAttributes(Profile.class.getName()).get(Profile.CANDIDATE_PROFILES_ATTRIB_NAME); - if (!this.environment.acceptsProfiles(specifiedProfiles)) { + if (Profile.Helper.isProfileAnnotationPresent(metadata)) { + if (!this.environment.acceptsProfiles(Profile.Helper.getCandidateProfiles(metadata))) { // TODO SPR-7508: log that this bean is being rejected on profile mismatch return; } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index bf696d13b87..c596447a97b 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -16,17 +16,15 @@ package org.springframework.context.annotation; -import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; /** * Standalone application context, accepting annotated classes as input - in particular * {@link org.springframework.context.annotation.Configuration @Configuration}-annotated * classes, but also plain {@link org.springframework.stereotype.Component @Components} - * and JSR-330 compliant classes using {@literal javax.inject} annotations. Allows for + * and JSR-330 compliant classes using {@code javax.inject} annotations. Allows for * registering classes one by one ({@link #register}) as well as for classpath scanning * ({@link #scan}). * @@ -49,15 +47,13 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex private final ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this); - { // TODO: rework this, it's a bit confusing - this.setEnvironment(this.getEnvironment()); - } /** * Create a new AnnotationConfigApplicationContext that needs to be populated - * through {@link #register} calls and then manually {@link #refresh refreshed}. + * through {@link #register} calls and then manually {@linkplain #refresh refreshed}. */ public AnnotationConfigApplicationContext() { + this.delegateEnvironment(super.getEnvironment()); } /** @@ -67,6 +63,7 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex * e.g. {@link Configuration @Configuration} classes */ public AnnotationConfigApplicationContext(Class... annotatedClasses) { + this(); register(annotatedClasses); refresh(); } @@ -77,16 +74,24 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex * @param basePackages the packages to check for annotated classes */ public AnnotationConfigApplicationContext(String... basePackages) { + this(); scan(basePackages); refresh(); } + /** - * TODO SPR-7508: document + * {@inheritDoc} + *

Delegates given environment to underlying {@link AnnotatedBeanDefinitionReader} + * and {@link ClassPathBeanDefinitionScanner} members. */ @Override public void setEnvironment(ConfigurableEnvironment environment) { super.setEnvironment(environment); + delegateEnvironment(environment); + } + + private void delegateEnvironment(ConfigurableEnvironment environment) { this.reader.setEnvironment(environment); this.scanner.setEnvironment(environment); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java index de79ccbb16a..5d65222a1c7 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java @@ -27,11 +27,11 @@ import org.springframework.beans.factory.annotation.Autowire; /** * Indicates that a method produces a bean to be managed by the Spring container. The * names and semantics of the attributes to this annotation are intentionally similar - * to those of the {@literal } element in the Spring XML schema. + * to those of the {@code } element in the Spring XML schema. * - *

Note that the @Bean annotation does not provide attributes for scope, - * primary or lazy. Rather, it should be used in conjunction with {@link Scope @Scope}, - * {@link Primary @Primary}, and {@link Lazy @Lazy} annotations to achieve + *

Note that the {@code @Bean} annotation does not provide attributes for scope, + * primary or lazy. Rather, it should be used in conjunction with {@link Scope @Scope}, + * {@link Primary @Primary}, and {@link Lazy @Lazy} annotations to achieve * those semantics. The same annotations can also be used at the type level, e.g. for * component scanning. * @@ -96,7 +96,7 @@ public @interface Bean { /** * The optional name of a method to call on the bean instance upon closing the - * application context, for example a {@literal close()} method on a {@literal DataSource}. + * application context, for example a {@code close()} method on a {@code DataSource}. * The method must have no arguments but may throw any exception. *

Note: Only invoked on beans whose lifecycle is under the full control of the * factory, which is always the case for singletons but not guaranteed diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 72662c44f62..ca9c47d73fe 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -116,15 +116,15 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC } /** - * TODO SPR-7508: document + * Set the Environment to use when resolving placeholders and evaluating + * {@link Profile @Profile}-annotated component classes. + *

The default is a {@link DefaultEnvironment} + * @param environment the Environment to use */ public void setEnvironment(Environment environment) { this.environment = environment; } - /** - * TODO SPR-7508: document - */ public Environment getEnvironment() { return this.environment; } @@ -280,7 +280,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC * @return the pattern specification to be used for package searching */ protected String resolveBasePackage(String basePackage) { - return ClassUtils.convertClassNameToResourcePath(environment.resolveRequiredPlaceholders(basePackage)); + return ClassUtils.convertClassNameToResourcePath(environment.getPropertyResolver().resolveRequiredPlaceholders(basePackage)); } /** @@ -298,12 +298,11 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); - if (!metadata.hasAnnotation(Profile.class.getName())) { + if (!Profile.Helper.isProfileAnnotationPresent(metadata)) { return true; } - String[] specifiedProfiles = - (String[])metadata.getAnnotationAttributes(Profile.class.getName()).get(Profile.CANDIDATE_PROFILES_ATTRIB_NAME); - return this.environment.acceptsProfiles(specifiedProfiles); + // TODO SPR-7508: log that this bean is being rejected on profile mismatch + return this.environment.acceptsProfiles(Profile.Helper.getCandidateProfiles(metadata)); } } return false; diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java index 9acfd337ad8..043a485ea3c 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java @@ -27,7 +27,7 @@ import org.springframework.beans.factory.support.BeanNameGenerator; /** * Configures component scanning directives for use with {@link Configuration} * classes. Provides support parallel with Spring XML's - * <context:component-scan> element. + * {@code } element. * * TODO SPR-7508: complete documentation. * diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index f9270f545a5..21d1fd3e56d 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -101,16 +101,14 @@ class ConfigurationClassParser { } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { - if (this.environment != null && configClass.getMetadata().hasAnnotation(Profile.class.getName())) { - String[] specifiedProfiles = - (String[])configClass.getMetadata().getAnnotationAttributes(Profile.class.getName()).get(Profile.CANDIDATE_PROFILES_ATTRIB_NAME); - if (!this.environment.acceptsProfiles(specifiedProfiles)) { + AnnotationMetadata metadata = configClass.getMetadata(); + if (this.environment != null && Profile.Helper.isProfileAnnotationPresent(metadata)) { + if (!this.environment.acceptsProfiles(Profile.Helper.getCandidateProfiles(metadata))) { // TODO SPR-7508: log that this bean is being rejected on profile mismatch return; } } - AnnotationMetadata metadata = configClass.getMetadata(); while (metadata != null) { doProcessConfigurationClass(configClass, metadata); String superClassName = metadata.getSuperClassName(); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 217df8d2eb2..aa5681648c8 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -49,8 +49,8 @@ import org.springframework.util.ClassUtils; * {@link BeanFactoryPostProcessor} used for bootstrapping processing of * {@link Configuration @Configuration} classes. * - *

Registered by default when using {@literal } or - * {@literal }. Otherwise, may be declared manually as + *

Registered by default when using {@code } or + * {@code }. Otherwise, may be declared manually as * with any other BeanFactoryPostProcessor. * *

This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it is important @@ -100,7 +100,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo /** * Set the {@link ProblemReporter} to use. *

Used to register any problems detected with {@link Configuration} or {@link Bean} - * declarations. For instance, an @Bean method marked as {@literal final} is illegal + * declarations. For instance, an @Bean method marked as {@code final} is illegal * and would be reported as a problem. Defaults to {@link FailFastProblemReporter}. */ public void setProblemReporter(ProblemReporter problemReporter) { @@ -110,7 +110,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo /** * Set the {@link MetadataReaderFactory} to use. *

Default is a {@link CachingMetadataReaderFactory} for the specified - * {@link #setBeanClassLoader bean class loader}. + * {@linkplain #setBeanClassLoader bean class loader}. */ public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) { Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null"); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/DependsOn.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/DependsOn.java index 7380032a4ab..1fee1a0f9fa 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/DependsOn.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/DependsOn.java @@ -38,7 +38,7 @@ import java.lang.annotation.Documented; *

Using {@link DependsOn} at the class level has no effect unless component-scanning * is being used. If a {@link DependsOn}-annotated class is declared via XML, * {@link DependsOn} annotation metadata is ignored, and - * {@literal } is respected instead. + * {@code } is respected instead. * * @author Juergen Hoeller * @since 3.0 diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Import.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Import.java index 61c14a9a6c5..4f406fdd663 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Import.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Import.java @@ -23,18 +23,18 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Indicates one or more {@link Configuration} classes to import. + * Indicates one or more {@link Configuration @Configuration} classes to import. * - *

Provides functionality equivalent to the {@literal } element in Spring XML. - * Only supported for actual {@literal @Configuration}-annotated classes. + *

Provides functionality equivalent to the {@code } element in Spring XML. + * Only supported for actual {@code @Configuration}-annotated classes. * - *

{@literal @Bean} definitions declared in imported {@literal @Configuration} classes + *

{@code @Bean} definitions declared in imported {@code @Configuration} classes * should be accessed by using {@link Autowired @Autowired} injection. Either the bean * itself can be autowired, or the configuration class instance declaring the bean can be * autowired. The latter approach allows for explicit, IDE-friendly navigation between - * {@literal @Configuration} class methods. + * {@code @Configuration} class methods. * - *

If XML or other non-{@literal @Configuration} bean definition resources need to be + *

If XML or other non-{@code @Configuration} bean definition resources need to be * imported, use {@link ImportResource @ImportResource} * * @author Chris Beams diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportResource.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportResource.java index 9aa10b99f5a..159ba67b647 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportResource.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportResource.java @@ -30,14 +30,14 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; * Indicates one or more resources containing bean definitions to import. * *

Like {@link Import @Import}, this annotation provides functionality similar to the - * {@literal } element in Spring XML. It is typically used when + * {@code } element in Spring XML. It is typically used when * designing {@link Configuration @Configuration} classes to be bootstrapped by * {@link AnnotationConfigApplicationContext}, but where some XML functionality such as * namespaces is still necessary. * *

By default, arguments to the {@link #value()} attribute will be processed using * an {@link XmlBeanDefinitionReader}, i.e. it is assumed that resources are Spring - * {@literal } XML files. Optionally, the {@link #reader()} attribute may be + * {@code } XML files. Optionally, the {@link #reader()} attribute may be * supplied, allowing the user to specify a different {@link BeanDefinitionReader} * implementation, such as * {@link org.springframework.beans.factory.support.PropertiesBeanDefinitionReader}. @@ -53,8 +53,8 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; public @interface ImportResource { /** - * Resource paths to import. Resource-loading prefixes such as {@literal classpath:} and - * {@literal file:}, etc may be used. + * Resource paths to import. Resource-loading prefixes such as {@code classpath:} and + * {@code file:}, etc may be used. */ String[] value(); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java index fa17b493978..0c0890f2aa4 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java @@ -25,25 +25,25 @@ import java.lang.annotation.Inherited; /** * Indicates whether a bean is to be lazily initialized. - * + * *

May be used on any class directly or indirectly annotated with * {@link org.springframework.stereotype.Component} or on methods annotated with * {@link Bean}. - * + * *

If this annotation is not present on a Component or Bean definition, eager - * initialization will occur. If present and set to {@literal true}, the + * initialization will occur. If present and set to {@code true}, the * Bean/Component will not be initialized until referenced by another bean or * explicitly retrieved from the enclosing * {@link org.springframework.beans.factory.BeanFactory}. If present and set to - * {@literal false}, the bean will be instantiated on startup by bean factories + * {@code false}, the bean will be instantiated on startup by bean factories * that perform eager initialization of singletons. - * - *

If Lazy is present on a {@link Configuration} class, this indicates that all - * {@link Bean} methods within that {@literal Configuration} should be lazily - * initialized. If Lazy is present and false on a Bean method within a - * Lazy-annotated Configuration class, this indicates overriding the 'default - * lazy' behavior and that the bean should be eagerly initialized. - * + * + *

If Lazy is present on a {@link Configuration @Configuration} class, this + * indicates that all {@link Bean @Bean} methods within that {@code @Configuration} + * should be lazily initialized. If Lazy is present and false on a Bean method + * within a Lazy-annotated Configuration class, this indicates overriding the + * 'default lazy' behavior and that the bean should be eagerly initialized. + * * @author Chris Beams * @since 3.0 * @see Primary diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java index 56307e81f32..91facf5f4f5 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java @@ -27,16 +27,16 @@ import java.lang.annotation.Target; * Indicates that a bean should be given preference when multiple candidates * are qualified to autowire a single-valued dependency. If exactly one 'primary' * bean exists among the candidates, it will be the autowired value. - * + * *

May be used on any class directly or indirectly annotated with * {@link org.springframework.stereotype.Component} or on methods annotated * with {@link Bean}. - * + * *

Using {@link Primary} at the class level has no effect unless component-scanning * is being used. If a {@link Primary}-annotated class is declared via XML, * {@link Primary} annotation metadata is ignored, and - * {@literal } is respected instead. - * + * {@code } is respected instead. + * * @author Chris Beams * @since 3.0 * @see Lazy diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Profile.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Profile.java index 6e6c2ea88cb..0ce97302b89 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Profile.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Profile.java @@ -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,42 +16,78 @@ package org.springframework.context.annotation; -import static java.lang.annotation.ElementType.ANNOTATION_TYPE; -import static java.lang.annotation.ElementType.TYPE; - +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.type.AnnotationMetadata; + /** - * TODO SPR-7508: document - * - * Components not @Profile-annotated will always be registered - * ConfigurableEnvironment.setActiveProfiles(String...) sets which profiles are active - * 'spring.profile.active' sets which profiles are active (typically as a -D system property) - servlet context/init param) - * ConfigurableEnvironment.setDefaultProfiles(String...) or 'spring.profile.default' property specifies one - or more default profiles, e.g., 'default' - * if 'default' is specified as a default profile, @Profile({"xyz,default}) means that beans will be - registered if 'xyz' is active or if no profile is active + * Indicates that a component is eligible for registration when one or more {@linkplain #value + * specified profiles} are active. + * + *

A profile is a named logical grouping that may be activated programatically via + * {@link ConfigurableEnvironment#setActiveProfiles} or declaratively through setting the + * {@link AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME spring.profile.active} property, + * usually through JVM system properties, as an environment variable, or for web applications + * as a Servlet context parameter in {@code web.xml}. + * + *

The {@code @Profile} annotation may be used in any of the following ways: + *

    + *
  • as a type-level annotation on any class directly or indirectly annotated with + * {@code @Component}, including {@link Configuration @Configuration} classes + *
  • as a meta-annotation, for the purpose of composing custom stereotype annotations + *
+ * + *

If a {@code @Configuration} class is marked with {@code @Profile}, all of the + * {@code @Bean} methods and {@link Import @Import} annotations associated with that class will + * be bypassed unless one or more the specified profiles are active. This is very similar to + * the behavior in Spring XML: if the {@code profile} attribute of the {@code beans} element is + * supplied e.g., {@code }, the {@code beans} element will not be parsed unless + * profiles 'p1' and/or 'p2' have been activated. Likewise, if a {@code @Component} or + * {@code @Configuration} class is marked with @Profile({"p1", "p2"}), that class will + * not be registered/processed unless profiles 'p1' and/or 'p2' have been activated. + * + *

If the {@code @Profile} annotation is omitted, registration will occur, regardless of which, + * if any, profiles are active. + * + *

When defining Spring beans via XML, the {@code "profile"} attribute of the {@code } + * element may be used. See the documentation in {@code spring-beans-3.1.xsd} for details. * * @author Chris Beams * @since 3.1 + * @see ConfigurableEnvironment#setActiveProfiles + * @see ConfigurableEnvironment#setDefaultProfiles + * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME + * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME */ @Retention(RetentionPolicy.RUNTIME) -@Target({ - ANNOTATION_TYPE, // @Profile may be used as a meta-annotation - TYPE // In conjunction with @Component and its derivatives -}) +@Target(ElementType.TYPE) public @interface Profile { /** - * @see #value() - */ - static final String CANDIDATE_PROFILES_ATTRIB_NAME = "value"; - - /** - * TODO SPR-7508: document + * The set profiles for which this component should be registered. */ String[] value(); + + + static class Helper { + /** + * Return whether the given metadata includes Profile information, whether directly or + * through meta-annotation. + */ + static boolean isProfileAnnotationPresent(AnnotationMetadata metadata) { + return metadata.isAnnotated(Profile.class.getName()); + } + + /** + * Return the String[] of candidate profiles from {@link Profile#value()}. + */ + static String[] getCandidateProfiles(AnnotationMetadata metadata) { + return (String[])metadata.getAnnotationAttributes(Profile.class.getName()).get("value"); + } + } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java index 55409be3139..248430dc311 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java @@ -35,7 +35,7 @@ import org.springframework.stereotype.Component; * the instance returned from the method. * *

In this context, scope means the lifecycle of an instance, such as - * {@literal singleton}, {@literal prototype}, and so forth. + * {@code singleton}, {@code prototype}, and so forth. * * @author Mark Fisher * @author Chris Beams @@ -59,7 +59,7 @@ public @interface Scope { * and if so, whether the proxy should be interface-based or subclass-based. *

Defaults to {@link ScopedProxyMode#NO}, indicating that no scoped * proxy should be created. - *

Analogous to {@literal } support in Spring XML. + *

Analogous to {@code } support in Spring XML. */ ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/package-info.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/package-info.java index 580f39a5215..53688cb9ccc 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/package-info.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/package-info.java @@ -1,10 +1,10 @@ /** - * + * * Annotation support for the Application Context, including JSR-250 "common" * annotations, component-scanning, and Java-based metadata for creating * Spring-managed objects. - * + * */ package org.springframework.context.annotation; diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java b/org.springframework.context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java index afbc1a12231..256f61e65f0 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/config/PropertyPlaceholderBeanDefinitionParser.java @@ -20,7 +20,7 @@ import org.w3c.dom.Element; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.context.support.EnvironmentAwarePropertyPlaceholderConfigurer; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.util.StringUtils; /** @@ -35,32 +35,24 @@ class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBea @Override protected Class getBeanClass(Element element) { - // as of Spring 3.1, the default for system-properties-mode is DELEGATE, - // meaning that the attribute should be disregarded entirely, instead - // deferring to the order of PropertySource objects in the enclosing - // application context's Environment object - if (!"DELEGATE".equals(element.getAttribute("system-properties-mode"))) { + if (element.hasAttribute("system-properties-mode")) { return PropertyPlaceholderConfigurer.class; } - return EnvironmentAwarePropertyPlaceholderConfigurer.class; + return PropertySourcesPlaceholderConfigurer.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { - super.doParse(element, builder); builder.addPropertyValue("ignoreUnresolvablePlaceholders", Boolean.valueOf(element.getAttribute("ignore-unresolvable"))); - if (!"DELEGATE".equals(element.getAttribute("system-properties-mode"))) { - String systemPropertiesModeName = element.getAttribute("system-properties-mode"); - if (StringUtils.hasLength(systemPropertiesModeName)) { - builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_"+systemPropertiesModeName); - } + String systemPropertiesModeName = element.getAttribute("system-properties-mode"); + if (StringUtils.hasLength(systemPropertiesModeName)) { + builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_"+systemPropertiesModeName); } - } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/expression/EnvironmentAccessor.java b/org.springframework.context/src/main/java/org/springframework/context/expression/EnvironmentAccessor.java index 0727a839c3e..6bd0542d7cd 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/expression/EnvironmentAccessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/expression/EnvironmentAccessor.java @@ -44,11 +44,11 @@ public class EnvironmentAccessor implements PropertyAccessor { } /** - * Access provided {@literal target} object by calling its {@link Environment#getProperty(String)} - * method with the provided {@literal name}. + * Access the given target object by resolving the given property name against the given target + * environment. */ public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(((Environment)target).getProperty(name)); + return new TypedValue(((Environment)target).getPropertyResolver().getProperty(name)); } /** diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 163f9945249..a09bec7fa2b 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -206,8 +206,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader /** Statically specified listeners */ private Set> applicationListeners = new LinkedHashSet>(); - /** TODO SPR-7508: document */ - private ConfigurableEnvironment environment = new DefaultEnvironment(); + /** Environment used by this context; initialized by {@link #createEnvironment()} */ + private ConfigurableEnvironment environment; /** @@ -224,6 +224,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader public AbstractApplicationContext(ApplicationContext parent) { this.parent = parent; this.resourcePatternResolver = getResourcePatternResolver(); + this.environment = this.createEnvironment(); } @@ -279,6 +280,14 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader return this.environment; } + /** + * {@inheritDoc} + *

Default value is determined by {@link #createEnvironment()}. Replacing the + * default with this method is one option but configuration through {@link + * #getEnvironment()} should also be considered. In either case, such modifications + * should be performed before {@link #refresh()}. + * @see org.springframework.context.support.AbstractApplicationContext#createEnvironment + */ public void setEnvironment(ConfigurableEnvironment environment) { this.environment = environment; } @@ -400,6 +409,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader return this.applicationListeners; } + /** + * Create and return a new {@link DefaultEnvironment}. + */ + protected ConfigurableEnvironment createEnvironment() { + return new DefaultEnvironment(); + } public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { @@ -456,7 +471,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader /** * Prepare this context for refreshing, setting its startup date and - * active flag. + * active flag as well as performing any initialization of property sources. */ protected void prepareRefresh() { this.startupDate = System.currentTimeMillis(); @@ -468,6 +483,18 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader if (logger.isInfoEnabled()) { logger.info("Refreshing " + this); } + + // Initialize any placeholder property sources in the context environment + initPropertySources(); + } + + /** + *

Replace any stub property sources with actual instances. + * @see org.springframework.core.env.PropertySource.StubPropertySource + * @see org.springframework.web.context.support.WebApplicationContextUtils#initSerlvetPropertySources + */ + protected void initPropertySources() { + // For subclasses: do nothing by default. } /** diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java index b0a7aa9c87a..b868db75aa4 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java @@ -28,7 +28,7 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer; /** * Base class for {@link org.springframework.context.ApplicationContext} - * implementations which are supposed to support multiple calls to {@literal refresh}, + * implementations which are supposed to support multiple calls to {@link #refresh()}, * creating a new internal bean factory instance every time. * Typically (but not necessarily), such a context will be driven by * a set of config locations to load bean definitions from. @@ -50,7 +50,7 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer; * and {@link FileSystemXmlApplicationContext}, which both derive from the * common {@link AbstractXmlApplicationContext} base class; * {@link org.springframework.context.annotation.AnnotationConfigApplicationContext} - * supports {@literal @Configuration}-annotated classes as a source of bean definitions. + * supports {@code @Configuration}-annotated classes as a source of bean definitions. * * @author Juergen Hoeller * @author Chris Beams @@ -182,7 +182,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl * Called for each {@link #refresh()} attempt. *

The default implementation creates a * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} - * with the {@link #getInternalParentBeanFactory() internal bean factory} of this + * with the {@linkplain #getInternalParentBeanFactory() internal bean factory} of this * context's parent as parent bean factory. Can be overridden in subclasses, * for example to customize DefaultListableBeanFactory's settings. * @return the bean factory for this context @@ -199,8 +199,8 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl * Customize the internal bean factory used by this context. * Called for each {@link #refresh()} attempt. *

The default implementation applies this context's - * {@link #setAllowBeanDefinitionOverriding "allowBeanDefinitionOverriding"} - * and {@link #setAllowCircularReferences "allowCircularReferences"} settings, + * {@linkplain #setAllowBeanDefinitionOverriding "allowBeanDefinitionOverriding"} + * and {@linkplain #setAllowCircularReferences "allowCircularReferences"} settings, * if specified. Can be overridden in subclasses to customize any of * {@link DefaultListableBeanFactory}'s settings. * @param beanFactory the newly created bean factory for this context diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableConfigApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableConfigApplicationContext.java index 79a40093232..66859badd2c 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableConfigApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableConfigApplicationContext.java @@ -17,11 +17,11 @@ package org.springframework.context.support; import org.springframework.beans.factory.BeanNameAware; + import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.util.SystemPropertyUtils; /** * {@link AbstractRefreshableApplicationContext} subclass that adds common handling @@ -117,14 +117,9 @@ public abstract class AbstractRefreshableConfigApplicationContext extends Abstra * system property values if necessary. Applied to config locations. * @param path the original file path * @return the resolved file path - * @see SystemPropertyUtils#resolveRequiredPlaceholders(String) */ protected String resolvePath(String path) { - // TODO SPR-7508: note that ARAC cannot delegate to its beanFactory's environment - // to call Environment.resolve[Required]Placeholders(String), as the bean factory - // has not yet been initialized. This amounts to one more reason not to use the ARAC - // hierarchy - it won't have early access to environment property resolution. - return SystemPropertyUtils.resolvePlaceholders(path); + return this.getEnvironment().getPropertyResolver().resolveRequiredPlaceholders(path); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/EnvironmentAwarePropertyPlaceholderConfigurer.java b/org.springframework.context/src/main/java/org/springframework/context/support/EnvironmentAwarePropertyPlaceholderConfigurer.java deleted file mode 100644 index ac9693a0f9f..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/support/EnvironmentAwarePropertyPlaceholderConfigurer.java +++ /dev/null @@ -1,115 +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.context.support; - -import java.util.LinkedList; -import java.util.Properties; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.AbstractPropertyPlaceholderConfigurer; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.AbstractEnvironment; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.core.env.PropertiesPropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.util.Assert; -import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; - - -/** - * TODO SPR-7508: document - * - * Local properties are added as a property source in any case. Precedence is based - * on the value of the {@link #setLocalOverride(boolean) localOverride} property. - * - * @author Chris Beams - * @since 3.1 - * @see PropertyPlaceholderConfigurer - * @see EnvironmentAwarePropertyOverrideConfigurer - */ -public class EnvironmentAwarePropertyPlaceholderConfigurer - extends AbstractPropertyPlaceholderConfigurer implements EnvironmentAware { - - private ConfigurableEnvironment environment; - private Environment wrappedEnvironment; - - public void setEnvironment(Environment environment) { - this.wrappedEnvironment = environment; - } - - @Override - protected PlaceholderResolver getPlaceholderResolver(Properties props) { - return new PlaceholderResolver() { - public String resolvePlaceholder(String placeholderName) { - return environment.getProperty(placeholderName); - } - }; - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - Assert.notNull(this.wrappedEnvironment, "Environment must not be null. Did you call setEnvironment()?"); - environment = new AbstractEnvironment() { }; - - LinkedList> propertySources = environment.getPropertySources(); - EnvironmentPropertySource environmentPropertySource = - new EnvironmentPropertySource("wrappedEnvironment", wrappedEnvironment); - - if (!this.localOverride) { - propertySources.add(environmentPropertySource); - } - - if (this.localProperties != null) { - int cx=0; - for (Properties localProps : this.localProperties) { - propertySources.add(new PropertiesPropertySource("localProperties"+cx++, localProps)); - } - } - - if (this.localOverride) { - propertySources.add(environmentPropertySource); - } - - super.postProcessBeanFactory(beanFactory); - } - - static class EnvironmentPropertySource extends PropertySource { - - public EnvironmentPropertySource(String name, Environment source) { - super(name, source); - } - - @Override - public boolean containsProperty(String key) { - return source.containsProperty(key); - } - - @Override - public String getProperty(String key) { - return source.getProperty(key); - } - - @Override - public int size() { - // TODO Auto-generated method stub - return source.getPropertyCount(); - } - } -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index ccd1323f51e..470af7f8260 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; -import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java index 24cc25e4ace..b276ba04a03 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java @@ -44,8 +44,8 @@ public class GenericXmlApplicationContext extends GenericApplicationContext { /** - * Create a new GenericXmlApplicationContext that needs to be populated - * through {@link #load} calls and then manually {@link #refresh refreshed}. + * Create a new GenericXmlApplicationContext that needs to be + * {@linkplain #load loaded} and then manually {@link #refresh refreshed}. */ public GenericXmlApplicationContext() { reader.setEnvironment(this.getEnvironment()); @@ -91,8 +91,9 @@ public class GenericXmlApplicationContext extends GenericApplicationContext { } /** - * Set a custom environment. Should be called before any call to - * {@link #load}. TODO SPR-7508: document + * {@inheritDoc} + *

Delegates the given environment to underlying {@link XmlBeanDefinitionReader}. + * Should be called before any call to {@link #load}. */ @Override public void setEnvironment(ConfigurableEnvironment environment) { diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java b/org.springframework.context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java new file mode 100644 index 00000000000..8a775d5e5b4 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java @@ -0,0 +1,134 @@ +/* + * 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.context.support; + +import java.io.IOException; +import java.util.Properties; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.config.AbstractPropertyPlaceholderConfigurer; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertyResolver; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.PropertySources; +import org.springframework.core.env.PropertySourcesPropertyResolver; +import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; + +/** + * Specialization of {@link AbstractPropertyPlaceholderConfigurer} + * + *

Local properties are added as a property source in any case. Precedence is based + * on the value of the {@link #setLocalOverride localOverride} property. + * + * @author Chris Beams + * @since 3.1 + * @see AbstractPropertyPlaceholderConfigurer + * @see PropertyPlaceholderConfigurer + */ +public class PropertySourcesPlaceholderConfigurer extends AbstractPropertyPlaceholderConfigurer + implements EnvironmentAware { + + /** + * {@value} is the name given to the {@link PropertySource} for the set of + * {@linkplain #mergeProperties() merged properties} supplied to this configurer. + */ + public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties"; + + private MutablePropertySources propertySources; + + private PropertyResolver propertyResolver; + + private Environment environment; + + + /** + * {@inheritDoc} + *

{@code PropertySources} from this environment will be searched when replacing ${...} placeholders + * @see #setPropertySources + * @see #postProcessBeanFactory + */ + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + /** + * Customize the set of {@link PropertySources} to be used by this configurer. + * Setting this property indicates that environment property sources and local + * properties should be ignored. + * @see #postProcessBeanFactory + */ + public void setPropertySources(PropertySources propertySources) { + this.propertySources = new MutablePropertySources(propertySources); + } + + @Override + protected PlaceholderResolver getPlaceholderResolver(Properties props) { + return new PlaceholderResolver() { + public String resolvePlaceholder(String placeholderName) { + return propertyResolver.getProperty(placeholderName); + } + }; + } + + /** + * {@inheritDoc} + *

Processing occurs by replacing ${...} placeholders in bean definitions by resolving each + * against this configurer's set of {@link PropertySources}, which includes: + *

    + *
  • all {@linkplain Environment#getPropertySources environment property sources}, if an + * {@code Environment} {@linkplain #setEnvironment is present} + *
  • {@linkplain #mergeProperties merged local properties}, if {@linkplain #setLocation any} + * {@linkplain #setLocations have} {@linkplain #setProperties been} + * {@linkplain #setPropertiesArray specified} + *
  • any property sources set by calling {@link #setPropertySources} + *
+ *

If {@link #setPropertySources} is called, environment and local properties will be + * ignored. This method is designed to give the user fine-grained control over property + * sources, and once set, the configurer makes no assumptions about adding additional sources. + */ + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (this.propertySources == null) { + this.propertySources = new MutablePropertySources(); + if (this.environment != null) { + this.propertySources.addAll(this.environment.getPropertySources()); + } + try { + PropertySource localPropertySource = + new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, this.mergeProperties()); + if (this.localOverride) { + this.propertySources.addFirst(localPropertySource); + } else { + this.propertySources.addLast(localPropertySource); + } + } + catch (IOException ex) { + throw new BeanInitializationException("Could not load properties", ex); + } + } + + this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources); + this.processProperties(beanFactory, this.propertyResolver.asProperties()); + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java b/org.springframework.context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java index 4d39882719a..ae62286051a 100644 --- a/org.springframework.context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java @@ -30,8 +30,6 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.DefaultEnvironment; import org.springframework.jndi.JndiLocatorSupport; import org.springframework.jndi.TypeMismatchNamingException; @@ -70,9 +68,6 @@ public class SimpleJndiBeanFactory extends JndiLocatorSupport implements BeanFac /** Cache of the types of nonshareable resources: bean name --> bean type */ private final Map resourceTypes = new HashMap(); - /** TODO SPR-7508: should be JNDI-specific environment */ - private ConfigurableEnvironment environment = new DefaultEnvironment(); - public SimpleJndiBeanFactory() { setResourceRef(true); diff --git a/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd b/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd index f5366683b2a..ad93b101270 100644 --- a/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd +++ b/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd @@ -90,42 +90,17 @@ + type="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" /> - - - - - - - - - - - - - - - - - - - diff --git a/org.springframework.context/src/test/java/example/profilescan/DevComponent.java b/org.springframework.context/src/test/java/example/profilescan/DevComponent.java new file mode 100644 index 00000000000..5126e8dd6cf --- /dev/null +++ b/org.springframework.context/src/test/java/example/profilescan/DevComponent.java @@ -0,0 +1,37 @@ +/* + * 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 example.profilescan; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Profile(DevComponent.PROFILE_NAME) +@Component +public @interface DevComponent { + + public static final String PROFILE_NAME = "dev"; + + String value() default ""; + +} \ No newline at end of file diff --git a/org.springframework.context/src/test/java/example/profilescan/ProfileMetaAnnotatedComponent.java b/org.springframework.context/src/test/java/example/profilescan/ProfileMetaAnnotatedComponent.java new file mode 100644 index 00000000000..68b9d2d639f --- /dev/null +++ b/org.springframework.context/src/test/java/example/profilescan/ProfileMetaAnnotatedComponent.java @@ -0,0 +1,25 @@ +/* + * 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 example.profilescan; + + +@DevComponent(ProfileMetaAnnotatedComponent.BEAN_NAME) +public class ProfileMetaAnnotatedComponent { + + public static final String BEAN_NAME = "profileMetaAnnotatedComponent"; + +} \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java index bf27e63b672..0a45a5d02e0 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java @@ -39,7 +39,9 @@ import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; +import example.profilescan.DevComponent; import example.profilescan.ProfileAnnotatedComponent; +import example.profilescan.ProfileMetaAnnotatedComponent; import example.scannable.FooDao; import example.scannable.FooService; import example.scannable.FooServiceImpl; @@ -207,6 +209,15 @@ public class ClassPathScanningCandidateComponentProviderTests { assertThat(ctx.containsBean(ProfileAnnotatedComponent.BEAN_NAME), is(true)); } + @Test + public void testIntegrationWithAnnotationConfigApplicationContext_validMetaAnnotatedProfile() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.getEnvironment().setActiveProfiles(DevComponent.PROFILE_NAME); + ctx.register(ProfileMetaAnnotatedComponent.class); + ctx.refresh(); + assertThat(ctx.containsBean(ProfileMetaAnnotatedComponent.BEAN_NAME), is(true)); + } + @Test public void testIntegrationWithAnnotationConfigApplicationContext_invalidProfile() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -216,6 +227,15 @@ public class ClassPathScanningCandidateComponentProviderTests { assertThat(ctx.containsBean(ProfileAnnotatedComponent.BEAN_NAME), is(false)); } + @Test + public void testIntegrationWithAnnotationConfigApplicationContext_invalidMetaAnnotatedProfile() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.getEnvironment().setActiveProfiles("other"); + ctx.register(ProfileMetaAnnotatedComponent.class); + ctx.refresh(); + assertThat(ctx.containsBean(ProfileMetaAnnotatedComponent.BEAN_NAME), is(false)); + } + @Test public void testIntegrationWithAnnotationConfigApplicationContext_defaultProfile() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java index 68379d78d38..2b3924bb4f4 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassAspectIntegrationTests.java @@ -40,7 +40,7 @@ import test.beans.TestBean; * particularly convenient syntax requiring no extra artifact for the aspect. * *

Currently it is assumed that the user is bootstrapping Configuration class processing via XML (using - * annotation-config or component-scan), and thus will also use {@literal } to enable + * annotation-config or component-scan), and thus will also use {@code } to enable * processing of the Aspect annotation. * * @author Chris Beams diff --git a/org.springframework.context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java b/org.springframework.context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java index c760e5d7eca..21c15807cf2 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/config/ContextNamespaceHandlerTests.java @@ -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. @@ -72,7 +72,7 @@ public class ContextNamespaceHandlerTests { @Test public void propertyPlaceholderEnvironmentProperties() throws Exception { - MockEnvironment env = MockEnvironment.withProperty("foo", "spam"); + MockEnvironment env = new MockEnvironment().withProperty("foo", "spam"); GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext(); applicationContext.setEnvironment(env); applicationContext.load(new ClassPathResource("contextNamespaceHandlerTests-simple.xml", getClass())); diff --git a/org.springframework.context/src/test/java/org/springframework/context/expression/EnvironmentAccessorIntegrationTests.java b/org.springframework.context/src/test/java/org/springframework/context/expression/EnvironmentAccessorIntegrationTests.java index 755b50acefc..4dda9faf6c6 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/expression/EnvironmentAccessorIntegrationTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/expression/EnvironmentAccessorIntegrationTests.java @@ -20,12 +20,11 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; -import java.util.HashMap; - import org.junit.Test; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.mock.env.MockPropertySource; import test.beans.TestBean; @@ -49,7 +48,7 @@ public class EnvironmentAccessorIntegrationTests { .getBeanDefinition()); GenericApplicationContext ctx = new GenericApplicationContext(bf); - ctx.getEnvironment().addPropertySource("testMap", new HashMap() {{ put("my.name", "myBean"); }}); + ctx.getEnvironment().getPropertySources().addFirst(new MockPropertySource().withProperty("my.name", "myBean")); ctx.refresh(); assertThat(ctx.getBean(TestBean.class).getName(), equalTo("myBean")); diff --git a/org.springframework.context/src/test/java/org/springframework/context/support/EnvironmentAwarePropertyPlaceholderConfigurerTests.java b/org.springframework.context/src/test/java/org/springframework/context/support/EnvironmentAwarePropertyPlaceholderConfigurerTests.java deleted file mode 100644 index 852330cefba..00000000000 --- a/org.springframework.context/src/test/java/org/springframework/context/support/EnvironmentAwarePropertyPlaceholderConfigurerTests.java +++ /dev/null @@ -1,91 +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.context.support; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; - -import org.junit.Test; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.mock.env.MockEnvironment; - -import test.beans.TestBean; - -/** - * Unit tests for {@link EnvironmentAwarePropertyPlaceholderConfigurer}. - * - * @author Chris Beams - * @since 3.1 - * @see EnvironmentAwarePropertyPlaceholderConfigurerTests - */ -public class EnvironmentAwarePropertyPlaceholderConfigurerTests { - - @Test(expected=IllegalArgumentException.class) - public void environmentNotNull() { - new EnvironmentAwarePropertyPlaceholderConfigurer().postProcessBeanFactory(new DefaultListableBeanFactory()); - } - - @Test - public void localPropertiesOverrideFalse() { - localPropertiesOverride(false); - } - - @Test - public void localPropertiesOverrideTrue() { - localPropertiesOverride(true); - } - - private void localPropertiesOverride(boolean override) { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.registerBeanDefinition("testBean", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${foo}") - .getBeanDefinition()); - - EnvironmentAwarePropertyPlaceholderConfigurer ppc = new EnvironmentAwarePropertyPlaceholderConfigurer(); - - ppc.setLocalOverride(override); - ppc.setProperties(MockEnvironment.withProperty("foo", "local").asProperties()); - ppc.setEnvironment(MockEnvironment.withProperty("foo", "enclosing")); - ppc.postProcessBeanFactory(bf); - if (override) { - assertThat(bf.getBean(TestBean.class).getName(), equalTo("local")); - } else { - assertThat(bf.getBean(TestBean.class).getName(), equalTo("enclosing")); - } - } - - @Test - public void simpleReplacement() { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.registerBeanDefinition("testBean", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${my.name}") - .getBeanDefinition()); - - MockEnvironment env = new MockEnvironment(); - env.setProperty("my.name", "myValue"); - - EnvironmentAwarePropertyPlaceholderConfigurer ppc = - new EnvironmentAwarePropertyPlaceholderConfigurer(); - ppc.setEnvironment(env); - ppc.postProcessBeanFactory(bf); - assertThat(bf.getBean(TestBean.class).getName(), equalTo("myValue")); - } - -} diff --git a/org.springframework.context/src/test/java/org/springframework/context/support/GenericXmlApplicationContextTests.java b/org.springframework.context/src/test/java/org/springframework/context/support/GenericXmlApplicationContextTests.java index 1ead59fe472..c6be2f1474a 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/support/GenericXmlApplicationContextTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/support/GenericXmlApplicationContextTests.java @@ -9,7 +9,7 @@ import org.springframework.util.ClassUtils; /** * Unit tests for {@link GenericXmlApplicationContext}. - * + * * See SPR-7530. * * @author Chris Beams diff --git a/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java b/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java new file mode 100644 index 00000000000..bae62fd624a --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java @@ -0,0 +1,163 @@ +/* + * 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.context.support; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; + +import java.util.Properties; + +import org.junit.Test; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.mock.env.MockPropertySource; + +import test.beans.TestBean; + +/** + * Unit tests for {@link PropertySourcesPlaceholderConfigurer}. + * + * @author Chris Beams + * @since 3.1 + */ +public class PropertySourcesPlaceholderConfigurerTests { + + @Test + public void replacementFromEnvironmentProperties() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + + MockEnvironment env = new MockEnvironment(); + env.setProperty("my.name", "myValue"); + + PropertySourcesPlaceholderConfigurer ppc = + new PropertySourcesPlaceholderConfigurer(); + ppc.setEnvironment(env); + ppc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).getName(), equalTo("myValue")); + } + + @Test + public void localPropertiesViaResource() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + + PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer(); + Resource resource = new ClassPathResource("PropertySourcesPlaceholderConfigurerTests.properties", this.getClass()); + pc.setLocation(resource); + pc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).getName(), equalTo("foo")); + } + + @Test + public void localPropertiesOverrideFalse() { + localPropertiesOverride(false); + } + + @Test + public void localPropertiesOverrideTrue() { + localPropertiesOverride(true); + } + + @Test + public void explicitPropertySources() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addLast(new MockPropertySource().withProperty("my.name", "foo")); + + PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer(); + pc.setPropertySources(propertySources); + pc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).getName(), equalTo("foo")); + } + + @Test + public void explicitPropertySourcesExcludesEnvironment() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addLast(new MockPropertySource()); + + PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer(); + pc.setPropertySources(propertySources); + pc.setEnvironment(new MockEnvironment().withProperty("my.name", "env")); + pc.setIgnoreUnresolvablePlaceholders(true); + pc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).getName(), equalTo("${my.name}")); + } + + @Test + @SuppressWarnings("serial") + public void explicitPropertySourcesExcludesLocalProperties() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addLast(new MockPropertySource()); + + PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer(); + pc.setPropertySources(propertySources); + pc.setProperties(new Properties() {{ put("my.name", "local"); }}); + pc.setIgnoreUnresolvablePlaceholders(true); + pc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).getName(), equalTo("${my.name}")); + } + + @SuppressWarnings("serial") + private void localPropertiesOverride(boolean override) { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${foo}") + .getBeanDefinition()); + + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + + ppc.setLocalOverride(override); + ppc.setProperties(new Properties() {{ setProperty("foo", "local"); }}); + ppc.setEnvironment(new MockEnvironment().withProperty("foo", "enclosing")); + ppc.postProcessBeanFactory(bf); + if (override) { + assertThat(bf.getBean(TestBean.class).getName(), equalTo("local")); + } else { + assertThat(bf.getBean(TestBean.class).getName(), equalTo("enclosing")); + } + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.properties b/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.properties new file mode 100644 index 00000000000..b8f69781481 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.properties @@ -0,0 +1 @@ +my.name=foo \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/mock/env/MockEnvironment.java b/org.springframework.context/src/test/java/org/springframework/mock/env/MockEnvironment.java index 2a6ec523686..e8f55721c26 100644 --- a/org.springframework.context/src/test/java/org/springframework/mock/env/MockEnvironment.java +++ b/org.springframework.context/src/test/java/org/springframework/mock/env/MockEnvironment.java @@ -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. @@ -19,30 +19,41 @@ package org.springframework.mock.env; import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.ConfigurableEnvironment; - /** * Simple {@link ConfigurableEnvironment} implementation exposing a * {@link #setProperty(String, String)} and {@link #withProperty(String, String)} * methods for testing purposes. * * @author Chris Beams + * @since 3.1 * @see MockPropertySource */ public class MockEnvironment extends AbstractEnvironment { private MockPropertySource propertySource = new MockPropertySource(); + /** + * Create a new {@code MockEnvironment} with a single {@link MockPropertySource}. + */ public MockEnvironment() { - getPropertySources().add(propertySource); + getPropertySources().addLast(propertySource); } + /** + * Set a property on the underlying {@link MockPropertySource} for this environment. + */ public void setProperty(String key, String value) { propertySource.setProperty(key, value); } - public static MockEnvironment withProperty(String key, String value) { - MockEnvironment environment = new MockEnvironment(); - environment.setProperty(key, value); - return environment; + /** + * Convenient synonym for {@link #setProperty} that returns the current instance. + * Useful for method chaining and fluent-style use. + * @return this {@link MockEnvironment} instance + * @see MockPropertySource#withProperty(String, String) + */ + public MockEnvironment withProperty(String key, String value) { + this.setProperty(key, value); + return this; } } diff --git a/org.springframework.context/src/test/java/org/springframework/mock/env/MockPropertySource.java b/org.springframework.context/src/test/java/org/springframework/mock/env/MockPropertySource.java index f1e7285c8ec..14f239e848e 100644 --- a/org.springframework.context/src/test/java/org/springframework/mock/env/MockPropertySource.java +++ b/org.springframework.context/src/test/java/org/springframework/mock/env/MockPropertySource.java @@ -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. @@ -19,24 +19,85 @@ 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: + *

+ * {@code
+ *   PropertySource source = new MockPropertySource().withProperty("foo", "bar");
+ * }
+ * 
+ * + * @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()); } - private MockPropertySource(Properties properties) { - super("mockProperties", 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.setProperty(key, value); + this.source.put(key, value); } - public static MockPropertySource withProperty(String key, String value) { - Properties properties = new Properties(); - properties.setProperty(key, value); - return new MockPropertySource(properties); + /** + * 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; } } diff --git a/org.springframework.context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-system.xml b/org.springframework.context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-system.xml index d5268c52bcc..9c475282fc0 100644 --- a/org.springframework.context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-system.xml +++ b/org.springframework.context/src/test/resources/org/springframework/context/config/contextNamespaceHandlerTests-system.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd - http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index 02f8c43a289..b840ffebef7 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -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 activeProfiles = new LinkedHashSet(); private Set defaultProfiles = new LinkedHashSet(); - private LinkedList> propertySources = new LinkedList>(); - 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 propertiesMap) { - addPropertySource(new MapPropertySource(name, propertiesMap)); - } - - public LinkedList> 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 getProperty(String key, Class 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 getRequiredProperty(String key, Class 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 getActiveProfiles() { + protected Set 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 getDefaultProfiles() { + public String[] getDefaultProfiles() { + return this.doGetDefaultProfiles().toArray(new String[]{}); + } + + protected Set 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 activeProfiles = this.doGetActiveProfiles(); + Set 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 getSystemEnvironment() { Map 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 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 activeProfiles = this.getActiveProfiles(); - Set 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); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java new file mode 100644 index 00000000000..89f22eb7741 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java @@ -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 getRequiredProperty(String key, Class 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); + } + }); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java b/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java index d24e402a244..be1de7174a4 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java @@ -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 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> getPropertySources(); + ConfigurablePropertyResolver getPropertyResolver(); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java new file mode 100644 index 00000000000..1623107da43 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java @@ -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); + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/DefaultEnvironment.java b/org.springframework.core/src/main/java/org/springframework/core/env/DefaultEnvironment.java index 6e6190e0079..9aa2e18a248 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/DefaultEnvironment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/DefaultEnvironment.java @@ -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. + *

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: + *

    + *
  1. {@linkplain AbstractEnvironment#getSystemProperties() system properties} + *
  2. {@linkplain AbstractEnvironment#getSystemEnvironment() system environment variables} + *
+ * + * 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. + * + *

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()}. + * + *

Example: adding a new property source with highest search priority

+ *
+ *   ConfigurableEnvironment environment = new DefaultEnvironment();
+ *   MutablePropertySources propertySources = environment.getPropertySources();
+ *   Map myMap = new HashMap();
+ *   myMap.put("xyz", "myValue");
+ *   propertySources.addFirst(new MapPropertySource("MY_MAP", myMap));
+ * 
+ * + *

Example: removing the default system properties property source

+ *
+ *   MutablePropertySources propertySources = environment.getPropertySources();
+ *   propertySources.remove(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)
+ * 
+ * + *

Example: mocking the system environment for testing purposes

+ *
+ *   MutablePropertySources propertySources = environment.getPropertySources();
+ *   MockPropertySource mockEnvVars = new MockPropertySource().withProperty("xyz", "myValue");
+ *   propertySources.replace(DefaultEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);
+ * 
+ * + * When an {@link Environment} is being used by an ApplicationContext, it is important + * that any such PropertySource manipulations be performed before 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: + *
    + *
  • {@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME} + *
  • {@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME} + *
+ * + *

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())); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java b/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java index b85ebc2a351..eb1153c5714 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java @@ -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: + *

    + *
  1. profiles
  2. + *
  3. properties
  4. + *
+ * + * A profile is a named, logical group of bean definitions to be registered with the + * container only if the given profile is active. 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}. + * + *

Properties 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. + * + *

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. + * + *

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 }. + * + *

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...)}. + * + *

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 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 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 getProperty(String key, Class targetType); - - /** - * TODO SPR-7508: document - */ - String getRequiredProperty(String key); - - /** - * TODO SPR-7508: document - */ - T getRequiredProperty(String key, Class 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)}. + * + *

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. + * + *

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 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)}. + * + *

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. + * + *

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 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); - } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/EnvironmentCapable.java b/org.springframework.core/src/main/java/org/springframework/core/env/EnvironmentCapable.java index 5705502aecd..6e96cb26d02 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/EnvironmentCapable.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/EnvironmentCapable.java @@ -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. + * + *

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. + * + *

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(); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/MapPropertySource.java b/org.springframework.core/src/main/java/org/springframework/core/env/MapPropertySource.java index 469c590cc97..2efadf30561 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/MapPropertySource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/MapPropertySource.java @@ -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} object. * * @author Chris Beams * @since 3.1 + * @see PropertiesPropertySource */ public class MapPropertySource extends PropertySource> { @@ -37,17 +31,14 @@ public class MapPropertySource extends PropertySource> { 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); } } \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/MutablePropertySources.java b/org.springframework.core/src/main/java/org/springframework/core/env/MutablePropertySources.java new file mode 100644 index 00000000000..9483c645775 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/env/MutablePropertySources.java @@ -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> propertySourceList = new LinkedList>(); + + 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> asList() { + return Collections.unmodifiableList(this.propertySourceList); + } + + public PropertySource get(String propertySourceName) { + return propertySourceList.get(propertySourceList.indexOf(PropertySource.named(propertySourceName))); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java index 98ac9647fbf..1b52a7c1e9b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java @@ -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()) + *

Note that because a {@code Properties} object is technically an {@code } + * {@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 { +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); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/PropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertyResolver.java new file mode 100644 index 00000000000..ccc2b828cc1 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertyResolver.java @@ -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 getProperty(String key, Class 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 getRequiredProperty(String key, Class 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; + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySource.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySource.java index 31ee0124a2c..18443dd28ca 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySource.java @@ -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. + * + *

{@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}. + * + *

{@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 { + 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: + *

    + *
  • they are the same instance + *
  • the {@code name} properties for both objects are equal + *
+ * + *

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 { 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 { 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. + * + *

Primarily for internal use, but given a collection of {@code PropertySource} objects, may be + * used as follows: + *

+	 * {@code  
+	 *   List> sources = new ArrayList>();
+	 *   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"));
+	 * }
+	 * 
+ * + *

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 { /** - * 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{ + public static class StubPropertySource extends PropertySource { + + 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); } } + } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/DefaultWebEnvironment.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySources.java similarity index 66% rename from org.springframework.core/src/main/java/org/springframework/core/env/DefaultWebEnvironment.java rename to org.springframework.core/src/main/java/org/springframework/core/env/PropertySources.java index 97c3986a0ac..ff0a6f4a51a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/DefaultWebEnvironment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySources.java @@ -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> asList(); + + int size(); + + boolean contains(String propertySourceName); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java new file mode 100644 index 00000000000..a8c31452dec --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java @@ -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 getProperty(String key, Class 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> 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; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java b/org.springframework.core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java index c31cb97d025..df310c6002b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java @@ -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} implementation that is backed by system properties or environment @@ -38,32 +39,16 @@ abstract class ReadOnlySystemAttributesMap implements Map { 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() { diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/ResourceEditor.java b/org.springframework.core/src/main/java/org/springframework/core/io/ResourceEditor.java index 6146040cc0c..8cca8d305c3 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/ResourceEditor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/ResourceEditor.java @@ -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 String locations - * e.g. "file:C:/myfile.txt" or - * "classpath:myfile.txt") to Resource - * properties instead of using a String 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. * *

The path may contain ${...} placeholders, to be * resolved as {@link Environment} properties: e.g. ${user.dir}. @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java index 89cb3786011..53d3c89faac 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java @@ -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); } } diff --git a/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java b/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java index 249de6439a7..a6d87b323ca 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java @@ -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[] toArray(Enumeration enumeration, A[] array) { + ArrayList elements = new ArrayList(); + while (enumeration.hasMoreElements()) { + elements.add(enumeration.nextElement()); + } + return elements.toArray(array); + } + + /** + * Adapt an enumeration to an iterator. * @param enumeration the enumeration * @return the iterator */ diff --git a/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java b/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java index 8fbcda5510f..6e8eaff2f13 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java @@ -25,12 +25,6 @@ import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; * ${user.dir}. 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 diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/DefaultEnvironmentTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/DefaultEnvironmentTests.java index b8c955e6f13..998e7da83e2 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/env/DefaultEnvironmentTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/env/DefaultEnvironmentTests.java @@ -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> 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> 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 nullableProperties = new HashMap(); - 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 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 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 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> isEmpty() { - - return new TypeSafeMatcher>() { - - @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 getModifiableSystemEnvironment() throws Exception { - Class[] classes = Collections.class.getDeclaredClasses(); - Map 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) obj; - } - } - throw new IllegalStateException(); + public void propertySourceOrder() { + ConfigurableEnvironment env = new DefaultEnvironment(); + List> 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)); } } diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentPropertyResolutionLateBindingTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentPropertyResolutionLateBindingTests.java deleted file mode 100644 index f307415e26a..00000000000 --- a/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentPropertyResolutionLateBindingTests.java +++ /dev/null @@ -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 - } -} diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentPropertyResolutionSearchTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentPropertyResolutionSearchTests.java deleted file mode 100644 index 308da06244c..00000000000 --- a/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentPropertyResolutionSearchTests.java +++ /dev/null @@ -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 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 getModifiableSystemEnvironment() throws Exception { - Class[] classes = Collections.class.getDeclaredClasses(); - Map 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) obj; - } - } - throw new IllegalStateException(); - } - -} diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentTests.java new file mode 100644 index 00000000000..da6149040a1 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentTests.java @@ -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 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 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 getModifiableSystemEnvironment() throws Exception { + Class[] classes = Collections.class.getDeclaredClasses(); + Map 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) obj; + } + } + throw new IllegalStateException(); + } +} diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/PropertyResolverTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/PropertyResolverTests.java new file mode 100644 index 00000000000..466ef83ffba --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/env/PropertyResolverTests.java @@ -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 nullableProperties = new HashMap(); + 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 map = new HashMap(); + 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 map = new HashMap(); + 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 { + 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); + } +} diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourceTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourceTests.java index 5877ad4bd9f..d2d7fd468bb 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourceTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourceTests.java @@ -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> propertySources = new LinkedList>(); + List> propertySources = new ArrayList>(); assertThat(propertySources.add(ps1), equalTo(true)); assertThat(propertySources.contains(ps1), is(true)); assertThat(propertySources.contains(PropertySource.named("ps1")), is(true)); diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesTests.java new file mode 100644 index 00000000000..83c8ac2a786 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesTests.java @@ -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"))); + } + } + +} diff --git a/org.springframework.core/src/test/java/org/springframework/mock/env/MockPropertySource.java b/org.springframework.core/src/test/java/org/springframework/mock/env/MockPropertySource.java new file mode 100644 index 00000000000..14f239e848e --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/mock/env/MockPropertySource.java @@ -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: + *

+ * {@code
+ *   PropertySource source = new MockPropertySource().withProperty("foo", "bar");
+ * }
+ * 
+ * + * @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; + } +} diff --git a/org.springframework.expression/src/test/java/org/springframework/mock/env/MockPropertySource.java b/org.springframework.expression/src/test/java/org/springframework/mock/env/MockPropertySource.java new file mode 100644 index 00000000000..14f239e848e --- /dev/null +++ b/org.springframework.expression/src/test/java/org/springframework/mock/env/MockPropertySource.java @@ -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: + *
+ * {@code
+ *   PropertySource source = new MockPropertySource().withProperty("foo", "bar");
+ * }
+ * 
+ * + * @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; + } +} diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/core/env/EnvironmentIntegrationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/core/env/EnvironmentIntegrationTests.java index 5c3332ed2ad..68504d6e188 100644 --- a/org.springframework.integration-tests/src/test/java/org/springframework/core/env/EnvironmentIntegrationTests.java +++ b/org.springframework.integration-tests/src/test/java/org/springframework/core/env/EnvironmentIntegrationTests.java @@ -18,9 +18,7 @@ package org.springframework.core.env; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isIn; import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; @@ -37,8 +35,6 @@ import static org.springframework.core.env.EnvironmentIntegrationTests.Constants import java.io.File; import java.io.IOException; -import java.util.LinkedList; -import java.util.Properties; import org.junit.Before; import org.junit.Test; @@ -64,16 +60,19 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.jca.context.ResourceAdapterApplicationContext; import org.springframework.jca.support.SimpleBootstrapContext; import org.springframework.jca.work.SimpleTaskWorkManager; +import org.springframework.mock.env.MockPropertySource; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; import org.springframework.util.FileCopyUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.context.support.DefaultWebEnvironment; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.portlet.context.AbstractRefreshablePortletApplicationContext; +import org.springframework.web.portlet.context.DefaultPortletEnvironment; import org.springframework.web.portlet.context.StaticPortletApplicationContext; import org.springframework.web.portlet.context.XmlPortletApplicationContext; @@ -433,34 +432,33 @@ public class EnvironmentIntegrationTests { ConfigurableEnvironment environment = ctx.getEnvironment(); assertThat(environment, instanceOf(DefaultWebEnvironment.class)); - LinkedList> propertySources = environment.getPropertySources(); - assertThat(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PARAMS_PROPERTY_SOURCE_NAME), isIn(propertySources)); - assertThat(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME), isIn(propertySources)); + MutablePropertySources propertySources = environment.getPropertySources(); + PropertyResolver propertyResolver = environment.getPropertyResolver(); + assertThat(propertySources.contains(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME), is(true)); + assertThat(propertySources.contains(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME), is(true)); // ServletConfig gets precedence - assertThat(environment.getProperty("pCommon"), is("pCommonConfigValue")); - assertThat(propertySources.indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME)), - lessThan(propertySources.indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PARAMS_PROPERTY_SOURCE_NAME)))); + assertThat(propertyResolver.getProperty("pCommon"), is("pCommonConfigValue")); + assertThat(propertySources.asList().indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME)), + lessThan(propertySources.asList().indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)))); // but all params are available - assertThat(environment.getProperty("pContext1"), is("pContext1Value")); - assertThat(environment.getProperty("pConfig1"), is("pConfig1Value")); + assertThat(propertyResolver.getProperty("pContext1"), is("pContext1Value")); + assertThat(propertyResolver.getProperty("pConfig1"), is("pConfig1Value")); // Servlet* PropertySources have precedence over System* PropertySources - assertThat(propertySources.indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME)), - lessThan(propertySources.indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)))); + assertThat(propertySources.asList().indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME)), + lessThan(propertySources.asList().indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)))); // Replace system properties with a mock property source for convenience MockPropertySource mockSystemProperties = new MockPropertySource(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME); mockSystemProperties.setProperty("pCommon", "pCommonSysPropsValue"); mockSystemProperties.setProperty("pSysProps1", "pSysProps1Value"); - propertySources.set( - propertySources.indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)), - mockSystemProperties); + propertySources.replace(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, mockSystemProperties); - // assert that servletconfig init params resolve with higher precedence than sysprops - assertThat(environment.getProperty("pCommon"), is("pCommonConfigValue")); - assertThat(environment.getProperty("pSysProps1"), is("pSysProps1Value")); + // assert that servletconfig params resolve with higher precedence than sysprops + assertThat(propertyResolver.getProperty("pCommon"), is("pCommonConfigValue")); + assertThat(propertyResolver.getProperty("pSysProps1"), is("pSysProps1Value")); } @Test @@ -475,29 +473,27 @@ public class EnvironmentIntegrationTests { ConfigurableEnvironment environment = ctx.getEnvironment(); assertThat(environment, instanceOf(DefaultWebEnvironment.class)); - LinkedList> propertySources = environment.getPropertySources(); - assertThat(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PARAMS_PROPERTY_SOURCE_NAME), isIn(propertySources)); - assertThat(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME), not(isIn(propertySources))); + MutablePropertySources propertySources = environment.getPropertySources(); + PropertyResolver propertyResolver = environment.getPropertyResolver(); + assertThat(propertySources.contains(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME), is(true)); // ServletContext params are available - assertThat(environment.getProperty("pCommon"), is("pCommonContextValue")); - assertThat(environment.getProperty("pContext1"), is("pContext1Value")); + assertThat(propertyResolver.getProperty("pCommon"), is("pCommonContextValue")); + assertThat(propertyResolver.getProperty("pContext1"), is("pContext1Value")); // Servlet* PropertySources have precedence over System* PropertySources - assertThat(propertySources.indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PARAMS_PROPERTY_SOURCE_NAME)), - lessThan(propertySources.indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)))); + assertThat(propertySources.asList().indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)), + lessThan(propertySources.asList().indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)))); // Replace system properties with a mock property source for convenience MockPropertySource mockSystemProperties = new MockPropertySource(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME); mockSystemProperties.setProperty("pCommon", "pCommonSysPropsValue"); mockSystemProperties.setProperty("pSysProps1", "pSysProps1Value"); - propertySources.set( - propertySources.indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)), - mockSystemProperties); + propertySources.replace(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, mockSystemProperties); // assert that servletcontext init params resolve with higher precedence than sysprops - assertThat(environment.getProperty("pCommon"), is("pCommonContextValue")); - assertThat(environment.getProperty("pSysProps1"), is("pSysProps1Value")); + assertThat(propertyResolver.getProperty("pCommon"), is("pCommonContextValue")); + assertThat(propertyResolver.getProperty("pSysProps1"), is("pSysProps1Value")); } @Test @@ -515,48 +511,33 @@ public class EnvironmentIntegrationTests { ctx.refresh(); ConfigurableEnvironment environment = ctx.getEnvironment(); - LinkedList> propertySources = environment.getPropertySources(); - assertThat(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PARAMS_PROPERTY_SOURCE_NAME), isIn(propertySources)); - assertThat(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME), isIn(propertySources)); + MutablePropertySources propertySources = environment.getPropertySources(); + PropertyResolver propertyResolver = environment.getPropertyResolver(); + assertThat(propertySources.contains(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME), is(true)); + assertThat(propertySources.contains(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME), is(true)); // ServletConfig gets precedence - assertThat(environment.getProperty("pCommon"), is("pCommonConfigValue")); - assertThat(propertySources.indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME)), - lessThan(propertySources.indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PARAMS_PROPERTY_SOURCE_NAME)))); + assertThat(propertyResolver.getProperty("pCommon"), is("pCommonConfigValue")); + assertThat(propertySources.asList().indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME)), + lessThan(propertySources.asList().indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)))); // but all params are available - assertThat(environment.getProperty("pContext1"), is("pContext1Value")); - assertThat(environment.getProperty("pConfig1"), is("pConfig1Value")); + assertThat(propertyResolver.getProperty("pContext1"), is("pContext1Value")); + assertThat(propertyResolver.getProperty("pConfig1"), is("pConfig1Value")); // Servlet* PropertySources have precedence over System* PropertySources - assertThat(propertySources.indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME)), - lessThan(propertySources.indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)))); + assertThat(propertySources.asList().indexOf(PropertySource.named(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME)), + lessThan(propertySources.asList().indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)))); // Replace system properties with a mock property source for convenience MockPropertySource mockSystemProperties = new MockPropertySource(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME); mockSystemProperties.setProperty("pCommon", "pCommonSysPropsValue"); mockSystemProperties.setProperty("pSysProps1", "pSysProps1Value"); - propertySources.set( - propertySources.indexOf(PropertySource.named(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)), - mockSystemProperties); + propertySources.replace(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, mockSystemProperties); - // assert that servletconfig init params resolve with higher precedence than sysprops - assertThat(environment.getProperty("pCommon"), is("pCommonConfigValue")); - assertThat(environment.getProperty("pSysProps1"), is("pSysProps1Value")); - } - - static class MockPropertySource extends PropertiesPropertySource { - public MockPropertySource() { - this("MockPropertySource"); - } - - public MockPropertySource(String name) { - super(name, new Properties()); - } - - public void setProperty(String key, String value) { - this.source.setProperty(key, value); - } + // assert that servletconfig params resolve with higher precedence than sysprops + assertThat(propertyResolver.getProperty("pCommon"), is("pCommonConfigValue")); + assertThat(propertyResolver.getProperty("pSysProps1"), is("pSysProps1Value")); } @Test @@ -580,8 +561,7 @@ public class EnvironmentIntegrationTests { public void staticPortletApplicationContext() { StaticPortletApplicationContext ctx = new StaticPortletApplicationContext(); - // TODO SPR-7508: should be a Portlet-specific environment? - assertHasDefaultWebEnvironment(ctx); + assertHasDefaultPortletEnvironment(ctx); registerEnvironmentBeanDefinition(ctx); @@ -638,6 +618,12 @@ public class EnvironmentIntegrationTests { assertThat(defaultEnv, instanceOf(DefaultWebEnvironment.class)); } + private void assertHasDefaultPortletEnvironment(WebApplicationContext ctx) { + Environment defaultEnv = ctx.getEnvironment(); + assertThat(defaultEnv, notNullValue()); + assertThat(defaultEnv, instanceOf(DefaultPortletEnvironment.class)); + } + private void assertHasEnvironment(ApplicationContext ctx, Environment expectedEnv) { // ensure the custom environment took Environment actualEnv = ctx.getEnvironment(); diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java index 30f7a55dc8b..4b984e7f577 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java @@ -103,7 +103,6 @@ public class SQLErrorCodesFactory { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); lbf.setBeanClassLoader(getClass().getClassLoader()); XmlBeanDefinitionReader bdr = new XmlBeanDefinitionReader(lbf); - // TODO: SPR-7508 consider setEnvironment() here // Load default SQL error codes. Resource resource = loadResource(SQL_ERROR_CODE_DEFAULT_PATH); diff --git a/org.springframework.test/src/main/java/org/springframework/mock/env/MockPropertySource.java b/org.springframework.test/src/main/java/org/springframework/mock/env/MockPropertySource.java new file mode 100644 index 00000000000..14f239e848e --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/mock/env/MockPropertySource.java @@ -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: + *
+ * {@code
+ *   PropertySource source = new MockPropertySource().withProperty("foo", "bar");
+ * }
+ * 
+ * + * @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; + } +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java b/org.springframework.transaction/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java index e9af714e540..ae041d3934e 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java @@ -166,7 +166,6 @@ public class SpringContextResourceAdapter implements ResourceAdapter { new ResourceAdapterApplicationContext(bootstrapContext); // Set ResourceAdapter's ClassLoader as bean class loader. applicationContext.setClassLoader(getClass().getClassLoader()); - // TODO: SPR-7508 consider setEnvironment() here // Extract individual config locations. String[] configLocations = StringUtils.tokenizeToStringArray(getContextConfigLocation(), CONFIG_LOCATION_DELIMITERS); @@ -185,7 +184,6 @@ public class SpringContextResourceAdapter implements ResourceAdapter { * @see #setContextConfigLocation */ protected void loadBeanDefinitions(BeanDefinitionRegistry registry, String[] configLocations) { - // TODO: SPR-7508 consider setEnvironment() here new XmlBeanDefinitionReader(registry).loadBeanDefinitions(configLocations); } diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/GenericPortletBean.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/GenericPortletBean.java index f73ce1b6db4..7336e282ea6 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/GenericPortletBean.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/GenericPortletBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 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. @@ -33,12 +33,13 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyAccessorFactory; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; -import org.springframework.core.env.DefaultWebEnvironment; +import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceEditor; import org.springframework.core.io.ResourceLoader; import org.springframework.util.StringUtils; +import org.springframework.web.portlet.context.DefaultPortletEnvironment; import org.springframework.web.portlet.context.PortletContextResourceLoader; /** @@ -64,7 +65,7 @@ import org.springframework.web.portlet.context.PortletContextResourceLoader; * @see #processAction * @see FrameworkPortlet */ -public abstract class GenericPortletBean extends GenericPortlet { +public abstract class GenericPortletBean extends GenericPortlet implements EnvironmentAware { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); @@ -75,10 +76,7 @@ public abstract class GenericPortletBean extends GenericPortlet { */ private final Set requiredProperties = new HashSet(); - /** - * TODO SPR-7508: think about making this overridable {@link EnvironmentAware}? - */ - private Environment environment = new DefaultWebEnvironment(); + private Environment environment = new DefaultPortletEnvironment(); /** @@ -167,6 +165,15 @@ public abstract class GenericPortletBean extends GenericPortlet { protected void initPortletBean() throws PortletException { } + /** + * {@inheritDoc} + *

Any environment set here overrides the {@link DefaultPortletEnvironment} + * provided by default. + */ + public void setEnvironment(Environment environment) { + this.environment = environment; + } + /** * PropertyValues implementation created from PortletConfig init parameters. diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java index 39ad0bdb735..c55cddaecd3 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractRefreshableConfigApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.web.context.ServletContextAware; @@ -150,6 +151,14 @@ public abstract class AbstractRefreshablePortletApplicationContext extends Abstr beanFactory, this.servletContext, this.portletContext, this.portletConfig); } + /** + * Create and return a new {@link DefaultPortletEnvironment}. + */ + @Override + protected ConfigurableEnvironment createEnvironment() { + return new DefaultPortletEnvironment(); + } + /** * This implementation supports file paths beneath the root of the PortletContext. * @see PortletContextResource diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/DefaultPortletEnvironment.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/DefaultPortletEnvironment.java new file mode 100644 index 00000000000..3491c92ae09 --- /dev/null +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/DefaultPortletEnvironment.java @@ -0,0 +1,79 @@ +/* + * 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.web.portlet.context; + +import javax.portlet.PortletConfig; +import javax.portlet.PortletContext; +import javax.servlet.ServletContext; + +import org.springframework.core.env.DefaultEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.PropertySource.StubPropertySource; +import org.springframework.web.context.support.DefaultWebEnvironment; + +/** + * {@link Environment} implementation to be used by {@code Servlet}-based web + * applications. All Portlet-related {@code ApplicationContext} classes initialize an instance + * by default. + * + *

Contributes {@code ServletContext}-, {@code PortletContext}-, and {@code PortletConfig}-based + * {@link PropertySource} instances. See the {@link #DefaultPortletEnvironment()} constructor + * for details. + * + * @author Chris Beams + * @since 3.1 + * @see DefaultEnvironment + * @see DefaultWebEnvironment + */ +public class DefaultPortletEnvironment extends DefaultEnvironment { + + /** Portlet context init parameters property source name: {@value} */ + public static final String PORTLET_CONTEXT_PROPERTY_SOURCE_NAME = "portletContextInitParams"; + + /** Portlet config init parameters property source name: {@value} */ + public static final String PORTLET_CONFIG_PROPERTY_SOURCE_NAME = "portletConfigInitParams"; + + /** + * Create a new {@code Environment} populated with the property sources contributed by + * superclasses as well as: + *

    + *
  • {@value #PORTLET_CONFIG_PROPERTY_SOURCE_NAME} + *
  • {@value #PORTLET_CONTEXT_PROPERTY_SOURCE_NAME} + *
  • {@linkplain DefaultWebEnvironment#SERVLET_CONTEXT_PROPERTY_SOURCE_NAME "servletContextInitParams"} + *
+ *

Properties present in {@value #PORTLET_CONFIG_PROPERTY_SOURCE_NAME} will + * take precedence over those in {@value #PORTLET_CONTEXT_PROPERTY_SOURCE_NAME}, + * which takes precedence over those in . + * Properties in either will take precedence over system properties and environment + * variables. + *

The property sources are added as stubs for now, and will be + * {@linkplain PortletApplicationContextUtils#initPortletPropertySources fully initialized} + * once the actual {@link PortletConfig}, {@link PortletContext}, and {@link ServletContext} + * objects are available. + * @see DefaultEnvironment#DefaultEnvironment + * @see PortletConfigPropertySource + * @see PortletContextPropertySource + * @see AbstractRefreshablePortletApplicationContext#initPropertySources + * @see PortletApplicationContextUtils#initPortletPropertySources + */ + public DefaultPortletEnvironment() { + this.getPropertySources().addFirst(new StubPropertySource(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); + this.getPropertySources().addFirst(new StubPropertySource(PORTLET_CONTEXT_PROPERTY_SOURCE_NAME)); + this.getPropertySources().addFirst(new StubPropertySource(PORTLET_CONFIG_PROPERTY_SOURCE_NAME)); + } +} diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java index 3d36f2d3a75..9a1ff5880d8 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; + import javax.portlet.PortletConfig; import javax.portlet.PortletContext; import javax.portlet.PortletRequest; @@ -30,6 +31,7 @@ import javax.servlet.ServletContext; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; +import org.springframework.core.env.MutablePropertySources; import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestAttributes; @@ -37,6 +39,7 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestScope; import org.springframework.web.context.request.SessionScope; import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.support.WebApplicationContextUtils; /** * Convenience methods for retrieving the root WebApplicationContext for a given @@ -184,6 +187,29 @@ public abstract class PortletApplicationContextUtils { } } + /** + * Replace {@code Servlet}- and {@code Portlet}-based stub property sources + * with actual instances populated with the given context and config objects. + * @see org.springframework.core.env.PropertySource.StubPropertySource + * @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources() + * @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources(MutablePropertySources, ServletContext) + */ + public static void initPortletPropertySources(MutablePropertySources propertySources, ServletContext servletContext, + PortletContext portletContext, PortletConfig portletConfig) { + Assert.notNull(propertySources, "propertySources must not be null"); + + WebApplicationContextUtils.initServletPropertySources(propertySources, servletContext); + + if(portletContext != null && propertySources.contains(DefaultPortletEnvironment.PORTLET_CONTEXT_PROPERTY_SOURCE_NAME)) { + propertySources.replace(DefaultPortletEnvironment.PORTLET_CONTEXT_PROPERTY_SOURCE_NAME, + new PortletContextPropertySource(DefaultPortletEnvironment.PORTLET_CONTEXT_PROPERTY_SOURCE_NAME, portletContext)); + } + if(portletConfig != null && propertySources.contains(DefaultPortletEnvironment.PORTLET_CONFIG_PROPERTY_SOURCE_NAME)) { + propertySources.replace(DefaultPortletEnvironment.PORTLET_CONFIG_PROPERTY_SOURCE_NAME, + new PortletConfigPropertySource(DefaultPortletEnvironment.PORTLET_CONFIG_PROPERTY_SOURCE_NAME, portletConfig)); + } + } + /** * Return the current RequestAttributes instance as PortletRequestAttributes. * @see RequestContextHolder#currentRequestAttributes() diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletConfigPropertySource.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletConfigPropertySource.java new file mode 100644 index 00000000000..94b4142f8ce --- /dev/null +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletConfigPropertySource.java @@ -0,0 +1,46 @@ +/* + * 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.web.portlet.context; + +import javax.portlet.PortletConfig; + +import org.springframework.core.env.PropertySource; +import org.springframework.util.CollectionUtils; + +/** + * {@link PropertySource} that reads init parameters from a {@link PortletConfig} object. + * + * @author Chris Beams + * @since 3.1 + * @see PortletContextPropertySource + */ +public class PortletConfigPropertySource extends PropertySource { + + public PortletConfigPropertySource(String name, PortletConfig portletConfig) { + super(name, portletConfig); + } + + @Override + public String[] getPropertyNames() { + return CollectionUtils.toArray(this.source.getInitParameterNames(), EMPTY_NAMES_ARRAY); + } + + @Override + public String getProperty(String name) { + return this.source.getInitParameter(name); + } +} diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletContextPropertySource.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletContextPropertySource.java new file mode 100644 index 00000000000..359960cac2c --- /dev/null +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletContextPropertySource.java @@ -0,0 +1,46 @@ +/* + * 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.web.portlet.context; + +import javax.portlet.PortletContext; + +import org.springframework.core.env.PropertySource; +import org.springframework.util.CollectionUtils; + +/** + * {@link PropertySource} that reads init parameters from a {@link PortletContext} object. + * + * @author Chris Beams + * @since 3.1 + * @see PortletConfigPropertySource + */ +public class PortletContextPropertySource extends PropertySource { + + public PortletContextPropertySource(String name, PortletContext portletContext) { + super(name, portletContext); + } + + @Override + public String[] getPropertyNames() { + return CollectionUtils.toArray(this.source.getInitParameterNames(), EMPTY_NAMES_ARRAY); + } + + @Override + public String getProperty(String name) { + return this.source.getInitParameter(name); + } +} diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java index 2746d8a1138..cac5932f5ff 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java @@ -23,7 +23,7 @@ import javax.servlet.ServletContext; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.StaticApplicationContext; -import org.springframework.core.env.DefaultWebEnvironment; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.web.context.WebApplicationContext; @@ -61,10 +61,29 @@ public class StaticPortletApplicationContext extends StaticApplicationContext public StaticPortletApplicationContext() { setDisplayName("Root Portlet ApplicationContext"); - setEnvironment(new DefaultWebEnvironment()); // TODO SPR-7508: create custom portlet env? } + /** + * Return a new {@link DefaultPortletEnvironment} + */ + @Override + protected ConfigurableEnvironment createEnvironment() { + return new DefaultPortletEnvironment(); + } + + /** + * {@inheritDoc} + *

Replace {@code Portlet}- and {@code Servlet}-related property sources. + */ + @Override + protected void initPropertySources() { + super.initPropertySources(); + PortletApplicationContextUtils.initPortletPropertySources( + this.getEnvironment().getPropertySources(), this.servletContext, + this.portletContext, this.portletConfig); + } + @Override public void setParent(ApplicationContext parent) { super.setParent(parent); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HttpServletBean.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HttpServletBean.java index f166bdbc96b..b2bb312ea25 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HttpServletBean.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HttpServletBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 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. @@ -35,12 +35,12 @@ import org.springframework.beans.PropertyAccessorFactory; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.DefaultWebEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceEditor; import org.springframework.core.io.ResourceLoader; import org.springframework.util.StringUtils; +import org.springframework.web.context.support.DefaultWebEnvironment; import org.springframework.web.context.support.ServletContextResourceLoader; /** @@ -75,7 +75,8 @@ import org.springframework.web.context.support.ServletContextResourceLoader; * @see #doGet * @see #doPost */ -public abstract class HttpServletBean extends HttpServlet { +@SuppressWarnings("serial") +public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); @@ -86,9 +87,6 @@ public abstract class HttpServletBean extends HttpServlet { */ private final Set requiredProperties = new HashSet(); - /** - * TODO SPR-7508: think about making this overridable {@link EnvironmentAware}? - */ private Environment environment = new DefaultWebEnvironment(); @@ -182,6 +180,15 @@ public abstract class HttpServletBean extends HttpServlet { protected void initServletBean() throws ServletException { } + /** + * {@inheritDoc} + *

Any environment set here overrides the {@link DefaultWebEnvironment} + * provided by default. + */ + public void setEnvironment(Environment environment) { + this.environment = environment; + } + /** * PropertyValues implementation created from ServletConfig init parameters. diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 7ed7cd4cd26..8540de9c2e0 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -35,9 +35,9 @@ import org.springframework.web.servlet.support.WebContentGenerator; /** * {@link HttpRequestHandler} that serves static resources optimized for superior browser performance * (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings - * ({@link #setCacheSeconds "cacheSeconds" property}, last-modified support). + * ({@linkplain #setCacheSeconds "cacheSeconds" property}, last-modified support). * - *

The {@link #setLocations "locations" property takes a list of Spring {@link Resource} locations + *

The {@linkplain #setLocations "locations" property} takes a list of Spring {@link Resource} locations * from which static resources are allowed to be served by this handler. For a given request, the * list of locations will be consulted in order for the presence of the requested resource, and the * first found match will be written to the response, with {@code Expires} and {@code Cache-Control} @@ -54,7 +54,7 @@ import org.springframework.web.servlet.support.WebContentGenerator; * using Spring EL. See the reference manual for further examples of this approach. * *

Rather than being directly configured as a bean, this handler will typically be configured - * through use of the <mvc:resources/> XML configuration element. + * through use of the {@code } XML configuration element. * * @author Keith Donald * @author Jeremy Grelle diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java index 4a54674408c..377cca40154 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2009 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. @@ -21,7 +21,7 @@ import javax.servlet.ServletContext; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.support.AbstractRefreshableConfigApplicationContext; -import org.springframework.core.env.DefaultWebEnvironment; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.ui.context.Theme; @@ -93,14 +93,11 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR public AbstractRefreshableWebApplicationContext() { setDisplayName("Root WebApplicationContext"); - setEnvironment(new DefaultWebEnvironment()); } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; - // TODO: SPR-7508 extract createEnvironment() method; do also in GWAC - this.getEnvironment().getPropertySources().addFirst(new ServletContextPropertySource(this.servletContext)); } public ServletContext getServletContext() { @@ -112,8 +109,6 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR if (servletConfig != null && this.servletContext == null) { this.setServletContext(servletConfig.getServletContext()); } - // TODO: SPR-7508 extract createEnvironment() method; do also in GWAC - this.getEnvironment().getPropertySources().addFirst(new ServletConfigPropertySource(servletConfig)); } public ServletConfig getServletConfig() { @@ -136,6 +131,14 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR return super.getConfigLocations(); } + /** + * Create and return a new {@link DefaultWebEnvironment}. + */ + @Override + protected ConfigurableEnvironment createEnvironment() { + return new DefaultWebEnvironment(); + } + /** * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc. @@ -176,6 +179,18 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR this.themeSource = UiApplicationContextUtils.initThemeSource(this); } + /** + * {@inheritDoc} + *

Replace {@code Servlet}-related property sources. + */ + @Override + protected void initPropertySources() { + super.initPropertySources(); + WebApplicationContextUtils.initServletPropertySources( + this.getEnvironment().getPropertySources(), this.servletContext, + this.servletConfig); + } + public Theme getTheme(String themeName) { return this.themeSource.getTheme(themeName); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index b126cb3ec33..1e8c0db0dd0 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -28,7 +28,7 @@ import org.springframework.context.annotation.ScopeMetadataResolver; * which accepts annotated classes as input - in particular * {@link org.springframework.context.annotation.Configuration @Configuration}-annotated * classes, but also plain {@link org.springframework.stereotype.Component @Components} - * and JSR-330 compliant classes using {@literal javax.inject} annotations. Allows for + * and JSR-330 compliant classes using {@code javax.inject} annotations. Allows for * registering classes one by one (specifying class names as config location) as well * as for classpath scanning (specifying base packages as config location). * @@ -46,7 +46,7 @@ import org.springframework.context.annotation.ScopeMetadataResolver; * FrameworkServlet. The param-value may contain both fully-qualified * class names and base packages to scan for components. * - *

Note: In case of multiple {@literal @Configuration} classes, later {@literal @Bean} + *

Note: In case of multiple {@code @Configuration} classes, later {@code @Bean} * definitions will override ones defined in earlier loaded files. This can be leveraged * to deliberately override certain bean definitions via an extra Configuration class. * @@ -60,10 +60,10 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe /** * Register a {@link BeanDefinition} for each class specified by {@link #getConfigLocations()}, * or scan each specified package for annotated classes. Enables the default set of - * annotation configuration post processors, such that {@literal @Autowired}, - * {@literal @Required}, and associated annotations can be used. + * annotation configuration post processors, such that {@code @Autowired}, + * {@code @Required}, and associated annotations can be used. *

Configuration class bean definitions are registered with generated bean definition - * names unless the {@literal value} attribute is provided to the stereotype annotation. + * names unless the {@code value} attribute is provided to the stereotype annotation. * @see #getConfigLocations() * @see AnnotatedBeanDefinitionReader * @see ClassPathBeanDefinitionScanner diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/DefaultWebEnvironment.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/DefaultWebEnvironment.java new file mode 100644 index 00000000000..4e942bb05d0 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/DefaultWebEnvironment.java @@ -0,0 +1,71 @@ +/* + * 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.web.context.support; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import org.springframework.core.env.DefaultEnvironment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.PropertySource.StubPropertySource; + +/** + * {@link Environment} implementation to be used by {@code Servlet}-based web + * applications. All web-related (servlet-based) {@code ApplicationContext} classes + * initialize an instance by default. + * + *

Contributes {@code ServletConfig}- and {@code ServletContext}-based {@link PropertySource} + * instances. See the {@link #DefaultWebEnvironment()} constructor for details. + * + * @author Chris Beams + * @since 3.1 + * @see DefaultEnvironment + * @see DefaultPortletEnvironment + */ +public class DefaultWebEnvironment extends DefaultEnvironment { + + /** Servlet context init parameters property source name: {@value} */ + public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams"; + + /** Servlet config init parameters property source name: {@value} */ + public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams"; + + /** + * Create a new {@code Environment} populated with the property sources contributed by + * superclasses as well as: + *

    + *
  • {@value #SERVLET_CONFIG_PROPERTY_SOURCE_NAME} + *
  • {@value #SERVLET_CONTEXT_PROPERTY_SOURCE_NAME} + *
+ *

Properties present in {@value #SERVLET_CONFIG_PROPERTY_SOURCE_NAME} will + * take precedence over those in {@value #SERVLET_CONTEXT_PROPERTY_SOURCE_NAME}. + * Properties in either will take precedence over system properties and environment + * variables. + *

The {@code Servlet}-related property sources are added as stubs for now, and will be + * {@linkplain WebApplicationContextUtils#initServletPropertySources fully initialized} + * once the actual {@link ServletConfig} and {@link ServletContext} objects are available. + * @see DefaultEnvironment#DefaultEnvironment + * @see ServletConfigPropertySource + * @see ServletContextPropertySource + * @see org.springframework.context.support.AbstractApplicationContext#initPropertySources + * @see WebApplicationContextUtils#initServletPropertySources + */ + public DefaultWebEnvironment() { + this.getPropertySources().addFirst(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); + this.getPropertySources().addFirst(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); + } +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java index 1604429071f..07afb7edf97 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2009 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. @@ -21,7 +21,7 @@ import javax.servlet.ServletContext; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.env.DefaultWebEnvironment; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.ui.context.Theme; @@ -61,12 +61,6 @@ public class GenericWebApplicationContext extends GenericApplicationContext private ThemeSource themeSource; - // override superclass definition of environment - // TODO SPR-7508: polish - { - this.setEnvironment(new DefaultWebEnvironment()); - } - /** * Create a new GenericWebApplicationContext. * @see #setServletContext @@ -123,6 +117,14 @@ public class GenericWebApplicationContext extends GenericApplicationContext } + /** + * Create and return a new {@link DefaultWebEnvironment}. + */ + @Override + protected ConfigurableEnvironment createEnvironment() { + return new DefaultWebEnvironment(); + } + /** * Register ServletContextAwareProcessor. * @see ServletContextAwareProcessor @@ -160,7 +162,17 @@ public class GenericWebApplicationContext extends GenericApplicationContext @Override protected void onRefresh() { this.themeSource = UiApplicationContextUtils.initThemeSource(this); - this.getEnvironment().getPropertySources().addFirst(new ServletContextPropertySource(servletContext)); + } + + /** + * {@inheritDoc} + *

Replace {@code Servlet}-related property sources. + */ + @Override + protected void initPropertySources() { + super.initPropertySources(); + WebApplicationContextUtils.initServletPropertySources( + this.getEnvironment().getPropertySources(), this.servletContext); } public Theme getTheme(String themeName) { diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletConfigPropertySource.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletConfigPropertySource.java index 61d8658d200..2cc7afc405d 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletConfigPropertySource.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletConfigPropertySource.java @@ -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. @@ -20,36 +20,27 @@ import java.util.Enumeration; import javax.servlet.ServletConfig; -import org.springframework.core.env.DefaultWebEnvironment; import org.springframework.core.env.PropertySource; - +import org.springframework.util.CollectionUtils; /** - * TODO SPR-7508: document - * + * {@link PropertySource} that reads init parameters from a {@link ServletConfig} object. + * * @author Chris Beams * @since 3.1 * @see ServletContextPropertySource */ public class ServletConfigPropertySource extends PropertySource { - public ServletConfigPropertySource(ServletConfig servletConfig) { - this(DefaultWebEnvironment.SERVLET_CONFIG_PARAMS_PROPERTY_SOURCE_NAME, servletConfig); - } - public ServletConfigPropertySource(String name, ServletConfig servletConfig) { super(name, servletConfig); } @Override - public boolean containsProperty(String name) { - Enumeration initParamNames = this.source.getInitParameterNames(); - while (initParamNames.hasMoreElements()) { - if (initParamNames.nextElement().equals(name)) { - return true; - } - } - return false; + @SuppressWarnings("unchecked") + public String[] getPropertyNames() { + return CollectionUtils.toArray( + (Enumeration)this.source.getInitParameterNames(), EMPTY_NAMES_ARRAY); } @Override @@ -57,14 +48,4 @@ public class ServletConfigPropertySource extends PropertySource { return this.source.getInitParameter(name); } - @Override - public int size() { - int size=0; - Enumeration initParamNames = this.source.getInitParameterNames(); - while (initParamNames.hasMoreElements()) { - initParamNames.nextElement(); - size++; - } - return size; - } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertyPlaceholderConfigurer.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertyPlaceholderConfigurer.java index 3a35563168b..adfc6176261 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertyPlaceholderConfigurer.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertyPlaceholderConfigurer.java @@ -21,12 +21,11 @@ import java.util.Properties; import javax.servlet.ServletContext; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.context.support.EnvironmentAwarePropertyPlaceholderConfigurer; import org.springframework.web.context.ServletContextAware; /** - * Subclass of PropertyPlaceholderConfigurer that resolves placeholders as + * Subclass of {@link PropertyPlaceholderConfigurer} that resolves placeholders as * ServletContext init parameters (that is, web.xml context-param * entries). * @@ -59,8 +58,8 @@ import org.springframework.web.context.ServletContextAware; * @see #setSearchContextAttributes * @see javax.servlet.ServletContext#getInitParameter(String) * @see javax.servlet.ServletContext#getAttribute(String) - * @deprecated in Spring 3.1 in favor of {@link EnvironmentAwarePropertyPlaceholderConfigurer} - * in conjunction with {@link org.springframework.core.env.DefaultWebEnvironment}. + * @deprecated in Spring 3.1 in favor of {@link org.springframework.context.support.PropertySourcesPlaceholderConfigurer} + * in conjunction with {@link org.springframework.web.context.support.DefaultWebEnvironment}. */ @Deprecated public class ServletContextPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertySource.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertySource.java index 8bcad5ab2f8..cee78c5105c 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertySource.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextPropertySource.java @@ -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. @@ -20,12 +20,11 @@ import java.util.Enumeration; import javax.servlet.ServletContext; -import org.springframework.core.env.DefaultWebEnvironment; import org.springframework.core.env.PropertySource; - +import org.springframework.util.CollectionUtils; /** - * TODO SPR-7508: document + * {@link PropertySource} that reads init parameters from a {@link ServletContext} object. * * @author Chris Beams * @since 3.1 @@ -33,38 +32,19 @@ import org.springframework.core.env.PropertySource; */ public class ServletContextPropertySource extends PropertySource { - public ServletContextPropertySource(ServletContext servletContext) { - this(DefaultWebEnvironment.SERVLET_CONTEXT_PARAMS_PROPERTY_SOURCE_NAME, servletContext); - } - public ServletContextPropertySource(String name, ServletContext servletContext) { super(name, servletContext); } @Override - public boolean containsProperty(String name) { - Enumeration initParamNames = this.source.getInitParameterNames(); - while (initParamNames.hasMoreElements()) { - if (initParamNames.nextElement().equals(name)) { - return true; - } - } - return false; + @SuppressWarnings("unchecked") + public String[] getPropertyNames() { + return CollectionUtils.toArray( + (Enumeration)this.source.getInitParameterNames(), EMPTY_NAMES_ARRAY); } @Override public String getProperty(String name) { return this.source.getInitParameter(name); } - - @Override - public int size() { - int size=0; - Enumeration initParamNames = this.source.getInitParameterNames(); - while (initParamNames.hasMoreElements()) { - initParamNames.nextElement(); - size++; - } - return size; - } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java index 395b50d1c2d..dfa8e8c37ce 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java @@ -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,14 +16,12 @@ package org.springframework.web.context.support; -import java.util.LinkedList; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.support.StaticApplicationContext; -import org.springframework.core.env.DefaultWebEnvironment; -import org.springframework.core.env.PropertySource; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.ui.context.Theme; @@ -69,7 +67,6 @@ public class StaticWebApplicationContext extends StaticApplicationContext public StaticWebApplicationContext() { setDisplayName("Root WebApplicationContext"); - setEnvironment(new DefaultWebEnvironment()); // TODO SPR-7508: see GenericWebApplicationContext, AbstractRefreshableWebApplicationContext } @@ -162,15 +159,27 @@ public class StaticWebApplicationContext extends StaticApplicationContext return new ServletContextResourcePatternResolver(this); } + /** + * Create and return a new {@link DefaultWebEnvironment}. + */ + @Override + protected ConfigurableEnvironment createEnvironment() { + return new DefaultWebEnvironment(); + } + /** * Initialize the theme capability. */ @Override protected void onRefresh() { this.themeSource = UiApplicationContextUtils.initThemeSource(this); - LinkedList> propertySources = this.getEnvironment().getPropertySources(); - propertySources.addFirst(new ServletContextPropertySource(servletContext)); - propertySources.addFirst(new ServletConfigPropertySource(servletConfig)); + } + + @Override + protected void initPropertySources() { + super.initPropertySources(); + WebApplicationContextUtils.initServletPropertySources( + this.getEnvironment().getPropertySources(), this.servletContext, this.servletConfig); } public Theme getTheme(String themeName) { diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java index 8e611e6b4d5..3be23bf2852 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; + import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.ServletConfig; @@ -30,6 +31,7 @@ import javax.servlet.http.HttpSession; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.env.MutablePropertySources; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.web.context.ConfigurableWebApplicationContext; @@ -192,14 +194,14 @@ public abstract class WebApplicationContextUtils { if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) { Map parameterMap = new HashMap(); if (sc != null) { - Enumeration paramNameEnum = sc.getInitParameterNames(); + Enumeration paramNameEnum = sc.getInitParameterNames(); while (paramNameEnum.hasMoreElements()) { String paramName = (String) paramNameEnum.nextElement(); parameterMap.put(paramName, sc.getInitParameter(paramName)); } } if (config != null) { - Enumeration paramNameEnum = config.getInitParameterNames(); + Enumeration paramNameEnum = config.getInitParameterNames(); while (paramNameEnum.hasMoreElements()) { String paramName = (String) paramNameEnum.nextElement(); parameterMap.put(paramName, config.getInitParameter(paramName)); @@ -212,7 +214,7 @@ public abstract class WebApplicationContextUtils { if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) { Map attributeMap = new HashMap(); if (sc != null) { - Enumeration attrNameEnum = sc.getAttributeNames(); + Enumeration attrNameEnum = sc.getAttributeNames(); while (attrNameEnum.hasMoreElements()) { String attrName = (String) attrNameEnum.nextElement(); attributeMap.put(attrName, sc.getAttribute(attrName)); @@ -223,6 +225,38 @@ public abstract class WebApplicationContextUtils { } } + /** + * Replace {@code Servlet}-based stub property sources with actual instances + * populated with the given context object. + * @see org.springframework.core.env.PropertySource.StubPropertySource + * @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources() + * @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources(MutablePropertySources, ServletContext) + */ + public static void initServletPropertySources( + MutablePropertySources propertySources, ServletContext servletContext) { + initServletPropertySources(propertySources, servletContext, null); + } + + /** + * Replace {@code Servlet}-based stub property sources with actual instances + * populated with the given context and config objects. + * @see org.springframework.core.env.PropertySource.StubPropertySource + * @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources(MutablePropertySources, ServletContext) + * @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources() + */ + public static void initServletPropertySources( + MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) { + Assert.notNull(propertySources, "propertySources must not be null"); + if(servletContext != null && propertySources.contains(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)) { + propertySources.replace(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, + new ServletContextPropertySource(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext)); + } + if(servletConfig != null && propertySources.contains(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME)) { + propertySources.replace(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, + new ServletConfigPropertySource(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig)); + } + } + /** * Return the current RequestAttributes instance as ServletRequestAttributes. * @see RequestContextHolder#currentRequestAttributes() @@ -239,6 +273,7 @@ public abstract class WebApplicationContextUtils { /** * Factory that exposes the current request object on demand. */ + @SuppressWarnings("serial") private static class RequestObjectFactory implements ObjectFactory, Serializable { public ServletRequest getObject() { @@ -255,6 +290,7 @@ public abstract class WebApplicationContextUtils { /** * Factory that exposes the current session object on demand. */ + @SuppressWarnings("serial") private static class SessionObjectFactory implements ObjectFactory, Serializable { public HttpSession getObject() { @@ -271,6 +307,7 @@ public abstract class WebApplicationContextUtils { /** * Factory that exposes the current WebRequest object on demand. */ + @SuppressWarnings("serial") private static class WebRequestObjectFactory implements ObjectFactory, Serializable { public WebRequest getObject() { diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/GenericFilterBean.java b/org.springframework.web/src/main/java/org/springframework/web/filter/GenericFilterBean.java index 288532600b8..5ead00a4d9f 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/filter/GenericFilterBean.java +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/GenericFilterBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 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. @@ -38,7 +38,6 @@ import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.DefaultWebEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceEditor; @@ -46,6 +45,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.context.ServletContextAware; +import org.springframework.web.context.support.DefaultWebEnvironment; import org.springframework.web.context.support.ServletContextResourceLoader; import org.springframework.web.util.NestedServletException; @@ -88,16 +88,10 @@ public abstract class GenericFilterBean implements */ private final Set requiredProperties = new HashSet(); - /* The FilterConfig of this filter */ private FilterConfig filterConfig; private String beanName; - /** - * TODO SPR-7508: document - * Defaults to {@link DefaultWebEnvironment}; can be overriden if deployed - * as a spring bean by {@link #setEnvironment(Environment)} - */ private Environment environment = new DefaultWebEnvironment(); private ServletContext servletContext; @@ -115,7 +109,9 @@ public abstract class GenericFilterBean implements } /** - * TODO SPR-7508: document + * {@inheritDoc} + *

Any environment set here overrides the {@link DefaultWebEnvironment} + * provided by default. */ public void setEnvironment(Environment environment) { this.environment = environment; @@ -282,6 +278,7 @@ public abstract class GenericFilterBean implements /** * PropertyValues implementation created from FilterConfig init parameters. */ + @SuppressWarnings("serial") private static class FilterConfigPropertyValues extends MutablePropertyValues { /** @@ -292,12 +289,12 @@ public abstract class GenericFilterBean implements * @throws ServletException if any required properties are missing */ public FilterConfigPropertyValues(FilterConfig config, Set requiredProperties) - throws ServletException { + throws ServletException { Set missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ? new HashSet(requiredProperties) : null; - Enumeration en = config.getInitParameterNames(); + Enumeration en = config.getInitParameterNames(); while (en.hasMoreElements()) { String property = (String) en.nextElement(); Object value = config.getInitParameter(property); diff --git a/org.springframework.web/src/test/java/org/springframework/web/context/support/DefaultWebEnvironmentTests.java b/org.springframework.web/src/test/java/org/springframework/web/context/support/DefaultWebEnvironmentTests.java new file mode 100644 index 00000000000..fdfb9ac9fba --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/context/support/DefaultWebEnvironmentTests.java @@ -0,0 +1,43 @@ +/* + * 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.web.context.support; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import org.junit.Test; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.DefaultEnvironment; +import org.springframework.core.env.PropertySource; + + +public class DefaultWebEnvironmentTests { + + @Test + public void propertySourceOrder() { + ConfigurableEnvironment env = new DefaultWebEnvironment(); + List> sources = env.getPropertySources().asList(); + assertThat(sources.size(), is(4)); + assertThat(sources.get(0).getName(), equalTo(DefaultWebEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); + assertThat(sources.get(1).getName(), equalTo(DefaultWebEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); + assertThat(sources.get(2).getName(), equalTo(DefaultEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)); + assertThat(sources.get(3).getName(), equalTo(DefaultEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)); + } +}