From 143db0d8de9d7b46e5de93323afa7a97152ac37c Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Sat, 26 Nov 2011 05:20:25 +0000 Subject: [PATCH] Introduce SystemEnvironmentPropertySource Properties such as 'spring.profiles.active' cannot be specified at the command line under Bash and other shells due to variable naming constraints. This change allows for exchanging underscores for periods as well as capitalizing property names for more idiomatic naming when dealing with environment variables. For example, Spring will respect equally either of the following: spring.profiles.active=p1 java -classpath ... MyApp SPRING_PROFILES_ACTIVE=p1 java -classpath ... MyApp The former is not possible under Bash, while the latter is. No code or configuration changes are required; SystemEnvironmentPropertySource adapts for these varations automatically. SystemEnvironmentPropertySource is added by default as "systemEnvironment" to StandardEnvironment and all subtypes, taking the place of the plain MapPropertySource that was in use before this change. Issue: SPR-8869 --- .../core/env/EnumerablePropertySource.java | 10 ++ .../core/env/StandardEnvironment.java | 3 +- .../env/SystemEnvironmentPropertySource.java | 132 ++++++++++++++++++ .../core/env/StandardEnvironmentTests.java | 7 + .../SystemEnvironmentPropertySourceTests.java | 104 ++++++++++++++ 5 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 org.springframework.core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java create mode 100644 org.springframework.core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java 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")); + } + +}