diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java b/org.springframework.core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java index ad1f95aabaa..a8767da54e2 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java @@ -16,6 +16,8 @@ package org.springframework.core.env; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; /** @@ -44,6 +46,8 @@ public abstract class EnumerablePropertySource extends PropertySource { protected static final String[] EMPTY_NAMES_ARRAY = new String[0]; + protected final Log logger = LogFactory.getLog(getClass()); + public EnumerablePropertySource(String name, T source) { super(name, source); @@ -65,9 +69,15 @@ public abstract class EnumerablePropertySource extends PropertySource { Assert.notNull(name, "property name must not be null"); for (String candidate : this.getPropertyNames()) { if (candidate.equals(name)) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("PropertySource [%s] contains '%s'", getName(), name)); + } return true; } } + if (logger.isTraceEnabled()) { + logger.trace(String.format("PropertySource [%s] does not contain '%s'", getName(), name)); + } return false; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/StandardEnvironment.java b/org.springframework.core/src/main/java/org/springframework/core/env/StandardEnvironment.java index 3d3344e394c..b36747200cf 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/StandardEnvironment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/StandardEnvironment.java @@ -44,6 +44,7 @@ package org.springframework.core.env; * @author Chris Beams * @since 3.1 * @see ConfigurableEnvironment + * @see SystemEnvironmentPropertySource * @see org.springframework.web.context.support.StandardServletEnvironment */ public class StandardEnvironment extends AbstractEnvironment { @@ -71,7 +72,7 @@ public class StandardEnvironment extends AbstractEnvironment { @Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); - propertySources.addLast(new MapPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); + propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java b/org.springframework.core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java new file mode 100644 index 00000000000..66eeb1e5bb0 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java @@ -0,0 +1,132 @@ +/* + * 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 java.util.Map; + +import org.springframework.util.Assert; + +/** + * Specialization of {@link MapPropertySource} designed for use with + * {@linkplain AbstractEnvironment#getSystemEnvironment() system environment variables}. + * Compensates for constraints in Bash and other shells that do not allow for variables + * containing the period character; also allows for uppercase variations on property + * names for more idiomatic shell use. + * + *

For example, a call to {@code getProperty("foo.bar")} will attempt to find a value + * for the original property or any 'equivalent' property, returning the first found: + *

    + *
  • {@code foo.bar} - the original name
  • + *
  • {@code foo_bar} - with underscores for periods (if any)
  • + *
  • {@code FOO.BAR} - original, with upper case
  • + *
  • {@code FOO_BAR} - with underscores and upper case
  • + *
+ * + * The same applies for calls to {@link #containsProperty(String)}, which returns + * {@code true} if any of the above properties are present, otherwise {@code false}. + * + *

This feature is particularly useful when specifying active or default profiles as + * environment variables. The following is not allowable under Bash + * + *

spring.profiles.active=p1 java -classpath ... MyApp
+ * + * However, the following syntax is permitted and is also more conventional. + * + *
SPRING_PROFILES_ACTIVE=p1 java -classpath ... MyApp
+ * + *

Enable debug- or trace-level logging for this class (or package) for messages + * explaining when these 'property name resolutions' occur. + * + *

This property source is included by default in {@link StandardEnvironment} and all + * its subclasses. + * + * @author Chris Beams + * @since 3.1 + * @see StandardEnvironment + * @see AbstractEnvironment#getSystemEnvironment() + * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME + */ +public class SystemEnvironmentPropertySource extends MapPropertySource { + + /** + * Create a new {@code SystemEnvironmentPropertySource} with the given name and + * delegating to the given {@code MapPropertySource}. + */ + public SystemEnvironmentPropertySource(String name, Map source) { + super(name, source); + } + + /** + * Return true if a property with the given name or any underscore/uppercase variant + * thereof exists in this property source. + */ + @Override + public boolean containsProperty(String name) { + return resolvePropertyName(name) != null; + } + + /** + * {@inheritDoc} + *

This implementation returns {@code true} if a property with the given name or + * any underscore/uppercase variant thereof exists in this property source. + */ + @Override + public Object getProperty(String name) { + Assert.notNull(name, "property name must not be null"); + String actualName = resolvePropertyName(name); + if (actualName == null) { + // at this point we know the property does not exist + return null; + } + if (logger.isDebugEnabled() && !name.equals(actualName)) { + logger.debug(String.format( + "PropertySource [%s] does not contain '%s', but found equivalent '%s'", + this.getName(), name, actualName)); + } + return super.getProperty(actualName); + } + + /** + * Check to see if this property source contains a property with the given name, or + * any underscore / uppercase variation thereof. Return the resolved name or + * {@code null} if none found. + */ + private String resolvePropertyName(String name) { + if (super.containsProperty(name)) { + return name; + } + + String usName = name.replace('.', '_'); + if (!name.equals(usName) && super.containsProperty(usName)) { + return usName; + } + + String ucName = name.toUpperCase(); + if (!name.equals(ucName)) { + if (super.containsProperty(ucName)) { + return ucName; + } else { + String usUcName = ucName.replace('.', '_'); + if (!ucName.equals(usUcName) && super.containsProperty(usUcName)) { + return usUcName; + } + } + } + + return null; + } +} diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java index ff5558c5dee..4a5881dcb71 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java @@ -71,6 +71,13 @@ public class StandardEnvironmentTests { assertThat(sources.size(), is(2)); } + @Test + public void propertySourceTypes() { + ConfigurableEnvironment env = new StandardEnvironment(); + MutablePropertySources sources = env.getPropertySources(); + assertThat(sources.get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME), instanceOf(SystemEnvironmentPropertySource.class)); + } + @Test public void activeProfilesIsEmptyByDefault() { assertThat(environment.getActiveProfiles().length, is(0)); diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java new file mode 100644 index 00000000000..880868eefdc --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java @@ -0,0 +1,104 @@ +/* + * 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 org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for {@link SystemEnvironmentPropertySource}. + * + * @author Chris Beams + * @since 3.1 + */ +public class SystemEnvironmentPropertySourceTests { + + private Map envMap; + private PropertySource ps; + + @Before + public void setUp() { + envMap = new HashMap(); + ps = new SystemEnvironmentPropertySource("sysEnv", envMap); + } + + @Test + public void none() { + //envMap.put("a.key", "a_value"); + + assertThat(ps.containsProperty("a.key"), equalTo(false)); + assertThat(ps.getProperty("a.key"), equalTo(null)); + } + + @Test + public void normalWithoutPeriod() { + envMap.put("akey", "avalue"); + + assertThat(ps.containsProperty("akey"), equalTo(true)); + assertThat(ps.getProperty("akey"), equalTo((Object)"avalue")); + } + + @Test + public void normalWithPeriod() { + envMap.put("a.key", "a.value"); + + assertThat(ps.containsProperty("a.key"), equalTo(true)); + assertThat(ps.getProperty("a.key"), equalTo((Object)"a.value")); + } + + @Test + public void withUnderscore() { + envMap.put("a_key", "a_value"); + + assertThat(ps.containsProperty("a_key"), equalTo(true)); + assertThat(ps.containsProperty("a.key"), equalTo(true)); + + assertThat(ps.getProperty("a_key"), equalTo((Object)"a_value")); + assertThat( ps.getProperty("a.key"), equalTo((Object)"a_value")); + } + + @Test + public void withBothPeriodAndUnderscore() { + envMap.put("a_key", "a_value"); + envMap.put("a.key", "a.value"); + + assertThat(ps.getProperty("a_key"), equalTo((Object)"a_value")); + assertThat( ps.getProperty("a.key"), equalTo((Object)"a.value")); + } + + @Test + public void withUppercase() { + envMap.put("A_KEY", "a_value"); + + assertThat(ps.containsProperty("A_KEY"), equalTo(true)); + assertThat(ps.containsProperty("A.KEY"), equalTo(true)); + assertThat(ps.containsProperty("a_key"), equalTo(true)); + assertThat(ps.containsProperty("a.key"), equalTo(true)); + + assertThat(ps.getProperty("A_KEY"), equalTo((Object)"a_value")); + assertThat(ps.getProperty("A.KEY"), equalTo((Object)"a_value")); + assertThat(ps.getProperty("a_key"), equalTo((Object)"a_value")); + assertThat(ps.getProperty("a.key"), equalTo((Object)"a_value")); + } + +}