diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 1ad4838ed88..223fdb65c83 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -38,6 +38,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ResourceLoader; @@ -179,13 +180,30 @@ class ConfigurationClassParser { if (propertySource != null) { String name = propertySource.getString("name"); String[] locations = propertySource.getStringArray("value"); + int nLocations = locations.length; + if (nLocations == 0) { + throw new IllegalArgumentException("At least one @PropertySource(value) location is required"); + } + for (int i = 0; i < nLocations; i++) { + locations[0] = this.environment.resolveRequiredPlaceholders(locations[0]); + } ClassLoader classLoader = this.resourceLoader.getClassLoader(); - for (String location : locations) { - location = this.environment.resolveRequiredPlaceholders(location); - ResourcePropertySource ps = StringUtils.hasText(name) ? - new ResourcePropertySource(name, location, classLoader) : - new ResourcePropertySource(location, classLoader); - this.propertySources.push(ps); + if (!StringUtils.hasText(name)) { + for (String location : locations) { + this.propertySources.push(new ResourcePropertySource(location, classLoader)); + } + } + else { + if (nLocations == 1) { + this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader)); + } + else { + CompositePropertySource ps = new CompositePropertySource(name); + for (String location : locations) { + ps.addPropertySource(new ResourcePropertySource(location, classLoader)); + } + this.propertySources.push(ps); + } } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java index 133b9639670..6649b008ca2 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java @@ -118,6 +118,25 @@ public class PropertySourceAnnotationTests { System.clearProperty("path.to.properties"); } + /** + * Corner bug reported in SPR-9127. + */ + @Test + public void withNameAndMultipleResourceLocations() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ConfigWithNameAndMultipleResourceLocations.class); + ctx.refresh(); + assertThat(ctx.getEnvironment().containsProperty("from.p1"), is(true)); + assertThat(ctx.getEnvironment().containsProperty("from.p2"), is(true)); + } + + @Test(expected=IllegalArgumentException.class) + public void withEmptyResourceLocations() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ConfigWithEmptyResourceLocations.class); + ctx.refresh(); + } + @Configuration @PropertySource(value="classpath:${unresolvable}/p1.properties") @@ -178,4 +197,21 @@ public class PropertySourceAnnotationTests { @PropertySource("classpath:org/springframework/context/annotation/p2.properties") static class P2Config { } + + + @Configuration + @PropertySource( + name = "psName", + value = { + "classpath:org/springframework/context/annotation/p1.properties", + "classpath:org/springframework/context/annotation/p2.properties" + }) + static class ConfigWithNameAndMultipleResourceLocations { + } + + + @Configuration + @PropertySource(value = {}) + static class ConfigWithEmptyResourceLocations { + } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/p1.properties b/spring-context/src/test/java/org/springframework/context/annotation/p1.properties index 55a32e793f9..39d7cef12b7 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/p1.properties +++ b/spring-context/src/test/java/org/springframework/context/annotation/p1.properties @@ -1 +1,2 @@ -testbean.name=p1TestBean \ No newline at end of file +testbean.name=p1TestBean +from.p1=p1Value \ No newline at end of file diff --git a/spring-context/src/test/java/org/springframework/context/annotation/p2.properties b/spring-context/src/test/java/org/springframework/context/annotation/p2.properties index 141de759548..34049d52a5e 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/p2.properties +++ b/spring-context/src/test/java/org/springframework/context/annotation/p2.properties @@ -1 +1,2 @@ -testbean.name=p2TestBean \ No newline at end of file +testbean.name=p2TestBean +from.p2=p2Value \ No newline at end of file diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index fcc067619dc..4e93c5a2784 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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,11 +16,8 @@ package org.springframework.core.env; -import static java.lang.String.format; -import static org.springframework.util.StringUtils.commaDelimitedListToStringArray; -import static org.springframework.util.StringUtils.trimAllWhitespace; - import java.security.AccessControlException; + import java.util.Collections; import java.util.LinkedHashSet; import java.util.Map; @@ -28,10 +25,14 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import static java.lang.String.*; +import static org.springframework.util.StringUtils.*; + /** * Abstract base class for {@link Environment} implementations. Supports the notion of * reserved default profile names and enables specifying active and default profiles @@ -89,19 +90,39 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { protected final Log logger = LogFactory.getLog(getClass()); private Set activeProfiles = new LinkedHashSet(); - private Set defaultProfiles = new LinkedHashSet(this.getReservedDefaultProfiles()); - private final MutablePropertySources propertySources = new MutablePropertySources(logger); - private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources); + private Set defaultProfiles = + new LinkedHashSet(this.getReservedDefaultProfiles()); + private final MutablePropertySources propertySources = + new MutablePropertySources(this.logger); + private final ConfigurablePropertyResolver propertyResolver = + new PropertySourcesPropertyResolver(this.propertySources); + + + /** + * Create a new {@code Environment} instance, calling back to + * {@link #customizePropertySources(MutablePropertySources)} during construction to + * allow subclasses to contribute or manipulate {@link PropertySource} instances as + * appropriate. + * @see #customizePropertySources(MutablePropertySources) + */ public AbstractEnvironment() { String name = this.getClass().getSimpleName(); - logger.debug(String.format("Initializing new %s", name)); - this.customizePropertySources(propertySources); - logger.debug(String.format("Initialized %s with PropertySources %s", name, propertySources)); + if (this.logger.isDebugEnabled()) { + this.logger.debug(format("Initializing new %s", name)); + } + + this.customizePropertySources(this.propertySources); + + if (this.logger.isDebugEnabled()) { + this.logger.debug(format( + "Initialized %s with PropertySources %s", name, this.propertySources)); + } } + /** * Customize the set of {@link PropertySource} objects to be searched by this * {@code Environment} during calls to {@link #getProperty(String)} and related @@ -163,6 +184,17 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { * env.getPropertySources().addLast(new PropertySourceX(...)); * * + *

A warning about instance variable access

+ * Instance variables declared in subclasses and having default initial values should + * not be accessed from within this method. Due to Java object creation + * lifecycle constraints, any initial value will not yet be assigned when this + * callback is invoked by the {@link #AbstractEnvironment()} constructor, which may + * lead to a {@code NullPointerException} or other problems. If you need to access + * default values of instance variables, leave this method as a no-op and perform + * property source manipulation and instance variable access directly within the + * subclass constructor. Note that assigning values to instance variables is + * not problematic; it is only attempting to read default values that must be avoided. + * * @see MutablePropertySources * @see PropertySourcesPropertyResolver * @see org.springframework.context.ApplicationContextInitializer @@ -217,7 +249,9 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } public void addActiveProfile(String profile) { - logger.debug(String.format("Activating profile '%s'", profile)); + if (this.logger.isDebugEnabled()) { + this.logger.debug(format("Activating profile '%s'", profile)); + } this.validateProfile(profile); this.activeProfiles.add(profile); } @@ -312,8 +346,10 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } catch (AccessControlException ex) { if (logger.isInfoEnabled()) { - logger.info(format("Caught AccessControlException when accessing system environment variable " + - "[%s]; its value will be returned [null]. Reason: %s", variableName, ex.getMessage())); + logger.info(format("Caught AccessControlException when " + + "accessing system environment variable [%s]; its " + + "value will be returned [null]. Reason: %s", + variableName, ex.getMessage())); } return null; } @@ -338,8 +374,10 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } catch (AccessControlException ex) { if (logger.isInfoEnabled()) { - logger.info(format("Caught AccessControlException when accessing system property " + - "[%s]; its value will be returned [null]. Reason: %s", propertyName, ex.getMessage())); + logger.info(format("Caught AccessControlException when " + + "accessing system property [%s]; its value will be " + + "returned [null]. Reason: %s", + propertyName, ex.getMessage())); } return null; } @@ -428,7 +466,8 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { @Override public String toString() { return format("%s {activeProfiles=%s, defaultProfiles=%s, propertySources=%s}", - getClass().getSimpleName(), this.activeProfiles, this.defaultProfiles, this.propertySources); + getClass().getSimpleName(), this.activeProfiles, this.defaultProfiles, + this.propertySources); } } diff --git a/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java new file mode 100644 index 00000000000..229c9cd2289 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.env; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Composite {@link PropertySource} implementation that iterates over a set of + * {@link PropertySource} instances. Necessary in cases where multiple property sources + * share the same name, e.g. when multiple values are supplied to {@code @PropertySource}. + * + * @author Chris Beams + * @since 3.1.1 + */ +public class CompositePropertySource extends PropertySource { + + private Set> propertySources = new LinkedHashSet>(); + + + /** + * Create a new {@code CompositePropertySource}. + * + * @param name the name of the property source + */ + public CompositePropertySource(String name) { + super(name); + } + + + @Override + public Object getProperty(String name) { + for (PropertySource propertySource : this.propertySources) { + Object candidate = propertySource.getProperty(name); + if (candidate != null) { + return candidate; + } + } + return null; + } + + public void addPropertySource(PropertySource propertySource) { + this.propertySources.add(propertySource); + } + + @Override + public String toString() { + return String.format("%s [name='%s', propertySources=%s]", + this.getClass().getSimpleName(), this.name, this.propertySources); + } +} diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java b/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java index 9f415bd8fb1..1faf7d715e4 100644 --- a/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java +++ b/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -108,6 +108,7 @@ class JcaListenerContainerParser extends AbstractListenerContainerParser { return containerDef; } + @Override protected boolean indicatesPubSub(BeanDefinition containerDef) { BeanDefinition configDef = (BeanDefinition) containerDef.getPropertyValues().getPropertyValue("activationSpecConfig").getValue(); diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerParser.java b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerParser.java index 444eb9a757e..e66a857155e 100644 --- a/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerParser.java +++ b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -47,6 +47,8 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser { private static final String CACHE_ATTRIBUTE = "cache"; + private static final String RECEIVE_TIMEOUT_ATTRIBUTE = "receive-timeout"; + protected BeanDefinition parseContainer(Element listenerEle, Element containerEle, ParserContext parserContext) { RootBeanDefinition containerDef = new RootBeanDefinition(); @@ -156,6 +158,13 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser { } } + String receiveTimeout = containerEle.getAttribute(RECEIVE_TIMEOUT_ATTRIBUTE); + if (StringUtils.hasText(receiveTimeout)) { + if (containerType.startsWith("default")) { + containerDef.getPropertyValues().add("receiveTimeout", new Integer(receiveTimeout)); + } + } + String phase = containerEle.getAttribute(PHASE_ATTRIBUTE); if (StringUtils.hasText(phase)) { containerDef.getPropertyValues().add("phase", phase); @@ -164,10 +173,12 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser { return containerDef; } + @Override protected boolean indicatesPubSub(BeanDefinition containerDef) { return indicatesPubSubConfig(containerDef); } + @Override protected boolean indicatesJms102(BeanDefinition containerDef) { return containerDef.getBeanClassName().endsWith("102"); } diff --git a/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-3.1.xsd b/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-3.1.xsd index b4cfb626ed3..08f9d634c90 100644 --- a/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-3.1.xsd +++ b/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-3.1.xsd @@ -233,6 +233,14 @@ ]]> + + + + + + http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.1.xsd"> + transaction-manager="testTransactionManager" error-handler="testErrorHandler" + concurrency="1-2" prefetch="50" receive-timeout="100" phase="99"> diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index fa14a36ddcd..f26431afc7d 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -3,10 +3,10 @@ SPRING FRAMEWORK CHANGELOG http://www.springsource.org -Changes in version 3.1.1 (2012-02-15) +Changes in version 3.1.1 (2012-02-16) ------------------------------------- -* official support for Hibernate 4.0 GA as well as Hibernate 4.1 +* official support for Hibernate 4.0.0/4.0.1 as well as Hibernate 4.1 * JBossNativeJdbcExtractor is compatible with JBoss AS 7 as well * restored JBossLoadTimeWeaver compatibility with JBoss AS 5.1 * Provider injection works with generically typed collections of beans as well @@ -34,20 +34,21 @@ Changes in version 3.1.1 (2012-02-15) * fixed QuartzJobBean and MethodInvokingJobDetailFactoryBean for compatibility with Quartz 2.0/2.1 * JMS CachingConnectionFactory never caches consumers for temporary queues and topics * JMS SimpleMessageListenerContainer silently falls back to lazy registration of consumers -* Servlet/PortletContextResource's "isReadable()" implementation returns false for directories +* added "receive-timeout" attribute to jms:listener-container element in JMS namespace +* ServletServerHttpRequest/Response fall back on the Content-Type and encoding of the request * preserve quotes in MediaType parameters * added "normalize()" method to UriComponents * remove empty path segments from input to UriComponentsBuilder * added "fromRequestUri(request)" and "fromCurrentRequestUri()" methods to ServletUriComponentsBuilder +* Servlet/PortletContextResource's "isReadable()" implementation returns false for directories * allow adding flash attributes in methods with a ModelAndView return value * make flash attributes available in the model of Parameterizable/UrlFilenameViewController * revised the FlashMapManager contract and implementation to address a flaw in its design -* improved @SessionAttributes handling to provide better support for clustered sessions -* added property to RedirectView to disable expanding URI variables in redirect URL +* removed check for HTTP POST when resolving multipart request controller method arguments * fixed request mapping bug involving direct vs pattern path matches with HTTP methods -* removed check for HTTP "POST" when resolving multipart request controller method arguments * updated @RequestMapping and reference docs wrt differences between @MVC 3.1 and @MVC 2.5-3.0 -* ServletServerHttpRequest/Response fall back on the Content-Type and encoding of the request +* improved @SessionAttributes handling to provide better support for clustered sessions +* added property to RedirectView to disable expanding URI variables in redirect URL Changes in version 3.1 GA (2011-12-12)