From 866c7847e34d0cf09fb1cf186bd39585c0bc6eaf Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 24 Jan 2014 16:58:16 +0100 Subject: [PATCH] Introduced SpringProperties class and optional "spring.properties" file This in particular allows for specifying "spring.getenv.ignore" and "spring.beaninfo.ignore" in a local way within the application, in case that JVM-level system properties are locked. Issue: SPR-9014 Issue: SPR-11297 (cherry picked from commit 8543b91) --- .../beans/CachedIntrospectionResults.java | 34 ++--- .../config/PropertyPlaceholderConfigurer.java | 3 +- .../core/SpringProperties.java | 130 ++++++++++++++++++ .../core/env/AbstractEnvironment.java | 23 ++-- .../core/env/StandardEnvironmentTests.java | 64 ++++++--- 5 files changed, 197 insertions(+), 57 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/SpringProperties.java diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index eb2cd89b9a3..3a080b582e3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -33,6 +33,7 @@ import java.util.WeakHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.SpringProperties; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -114,17 +115,7 @@ public class CachedIntrospectionResults { static { - boolean ignoreValue; - try { - ignoreValue = "true".equalsIgnoreCase(System.getProperty(IGNORE_BEANINFO_PROPERTY_NAME)); - } - catch (Throwable ex) { - if (logger.isDebugEnabled()) { - logger.debug("Could not obtain system property '" + IGNORE_BEANINFO_PROPERTY_NAME + "': " + ex); - } - ignoreValue = false; - } - shouldIntrospectorIgnoreBeaninfoClasses = ignoreValue; + shouldIntrospectorIgnoreBeaninfoClasses = SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME); } @@ -293,16 +284,19 @@ public class CachedIntrospectionResults { } this.beanInfo = beanInfo; - // Immediately remove class from Introspector cache, to allow for proper - // garbage collection on class loader shutdown - we cache it here anyway, - // in a GC-friendly manner. In contrast to CachedIntrospectionResults, - // Introspector does not use WeakReferences as values of its WeakHashMap! - Class classToFlush = beanClass; - do { - Introspector.flushFromCaches(classToFlush); - classToFlush = classToFlush.getSuperclass(); + // Only bother with flushFromCaches if the Introspector actually cached... + if (!shouldIntrospectorIgnoreBeaninfoClasses) { + // Immediately remove class from Introspector cache, to allow for proper + // garbage collection on class loader shutdown - we cache it here anyway, + // in a GC-friendly manner. In contrast to CachedIntrospectionResults, + // Introspector does not use WeakReferences as values of its WeakHashMap! + Class classToFlush = beanClass; + do { + Introspector.flushFromCaches(classToFlush); + classToFlush = classToFlush.getSuperclass(); + } + while (classToFlush != null); } - while (classToFlush != null); if (logger.isTraceEnabled()) { logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]"); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java index b631968085e..57e6800ac76 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java @@ -21,6 +21,7 @@ import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.core.Constants; +import org.springframework.core.SpringProperties; import org.springframework.core.env.AbstractEnvironment; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; @@ -84,7 +85,7 @@ public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK; private boolean searchSystemEnvironment = - !"true".equalsIgnoreCase(System.getProperty(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME)); + !SpringProperties.getFlag(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME); /** diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java new file mode 100644 index 00000000000..fe6def84bea --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java @@ -0,0 +1,130 @@ +/* + * Copyright 2002-2013 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; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Static holder for local Spring properties, i.e. defined at the Spring library level. + * + *

Reads a {@code spring.properties} file from the root of the Spring library classpath, + * and also allows for programmatically setting properties through {@link #setProperty}. + * When checking a property, local entries are being checked first, then falling back + * to JVM-level system properties through a {@link System#getProperty} check. + * + *

This is an alternative way to set Spring-related system properties such as + * "spring.getenv.ignore" and "spring.beaninfo.ignore", in particular for scenarios + * where JVM system properties are locked on the target platform (e.g. WebSphere). + * See {@link #setFlag} for a convenient way to locally set such flags to "true". + * + * @author Juergen Hoeller + * @since 3.2.7 + * @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME + * @see org.springframework.beans.CachedIntrospectionResults#IGNORE_BEANINFO_PROPERTY_NAME + */ +public abstract class SpringProperties { + + private static final Log logger = LogFactory.getLog(SpringProperties.class); + + private static final Properties localProperties = new Properties(); + + + static { + try { + ClassLoader cl = SpringProperties.class.getClassLoader(); + URL url = cl.getResource("spring.properties"); + if (url != null) { + logger.info("Found 'spring.properties' file in local classpath"); + InputStream is = url.openStream(); + try { + localProperties.load(is); + } + finally { + is.close(); + } + } + } + catch (IOException ex) { + if (logger.isInfoEnabled()) { + logger.info("Could not load 'spring.properties' file from local classpath: " + ex); + } + } + } + + + /** + * Programmatically set a local property, overriding an entry in the + * {@code spring.properties} file (if any). + * @param key the property key + * @param value the associated property value, or {@code null} to reset it + */ + public static void setProperty(String key, String value) { + if (value != null) { + localProperties.setProperty(key, value); + } + else { + localProperties.remove(key); + } + } + + /** + * Retrieve the property value for the given key, checking local Spring + * properties first and falling back to JVM-level system properties. + * @param key the property key + * @return the associated property value, or {@code null} if none found + */ + public static String getProperty(String key) { + String value = localProperties.getProperty(key); + if (value == null) { + try { + value = System.getProperty(key); + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not retrieve system property '" + key + "': " + ex); + } + } + } + return value; + } + + /** + * Programmatically set a local flag to "true", overriding an + * entry in the {@code spring.properties} file (if any). + * @param key the property key + */ + public static void setFlag(String key) { + localProperties.put(key, Boolean.TRUE.toString()); + } + + /** + * Retrieve the flag for the given property key. + * @param key the property key + * @return {@code true} if the property is set to "true", + * {@code} false otherwise + */ + public static boolean getFlag(String key) { + return Boolean.parseBoolean(getProperty(key)); + } + +} 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 a8a5458f575..197bff8e0c3 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 @@ -25,6 +25,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.SpringProperties; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -369,15 +370,15 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { catch (AccessControlException ex) { return (Map) new ReadOnlySystemAttributesMap() { @Override - protected String getSystemAttribute(String variableName) { + protected String getSystemAttribute(String attributeName) { try { - return System.getenv(variableName); + return System.getenv(attributeName); } 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())); + attributeName, ex.getMessage())); } return null; } @@ -396,15 +397,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { * returning {@code true} if its value equals "true" in any case. */ protected boolean suppressGetenvAccess() { - try { - return "true".equalsIgnoreCase(System.getProperty(IGNORE_GETENV_PROPERTY_NAME)); - } - catch (Throwable ex) { - if (logger.isDebugEnabled()) { - logger.debug("Could not obtain system property '" + IGNORE_GETENV_PROPERTY_NAME + "': " + ex); - } - return false; - } + return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME); } @SuppressWarnings("unchecked") @@ -415,15 +408,15 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { catch (AccessControlException ex) { return (Map) new ReadOnlySystemAttributesMap() { @Override - protected String getSystemAttribute(String propertyName) { + protected String getSystemAttribute(String attributeName) { try { - return System.getProperty(propertyName); + return System.getProperty(attributeName); } 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())); + attributeName, ex.getMessage())); } return null; } diff --git a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java index 131917c8ccf..964ba389875 100644 --- a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java @@ -17,19 +17,17 @@ package org.springframework.core.env; 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; +import org.springframework.core.SpringProperties; +import org.springframework.mock.env.MockPropertySource; -import static java.lang.String.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.core.env.AbstractEnvironment.*; @@ -38,6 +36,7 @@ import static org.springframework.core.env.AbstractEnvironment.*; * Unit tests for {@link StandardEnvironment}. * * @author Chris Beams + * @author Juergen Hoeller */ public class StandardEnvironmentTests { @@ -60,15 +59,15 @@ public class StandardEnvironmentTests { child.setActiveProfiles("c1", "c2"); child.getPropertySources().addLast( new MockPropertySource("childMock") - .withProperty("childKey", "childVal") - .withProperty("bothKey", "childBothVal")); + .withProperty("childKey", "childVal") + .withProperty("bothKey", "childBothVal")); ConfigurableEnvironment parent = new StandardEnvironment(); parent.setActiveProfiles("p1", "p2"); parent.getPropertySources().addLast( new MockPropertySource("parentMock") - .withProperty("parentKey", "parentVal") - .withProperty("bothKey", "parentBothVal")); + .withProperty("parentKey", "parentVal") + .withProperty("bothKey", "parentBothVal")); assertThat(child.getProperty("childKey"), is("childVal")); assertThat(child.getProperty("parentKey"), nullValue()); @@ -334,12 +333,27 @@ public class StandardEnvironmentTests { try { env.addActiveProfile("invalid-profile"); fail("expected validation exception"); - } catch (IllegalArgumentException ex) { + } + catch (IllegalArgumentException ex) { assertThat(ex.getMessage(), equalTo("Invalid profile [invalid-profile]: must not contain dash character")); } } + @Test + public void suppressGetenvAccessThroughSystemProperty() { + System.setProperty("spring.getenv.ignore", "true"); + assertTrue(environment.getSystemEnvironment().isEmpty()); + System.clearProperty("spring.getenv.ignore"); + } + + @Test + public void suppressGetenvAccessThroughSpringProperty() { + SpringProperties.setProperty("spring.getenv.ignore", "true"); + assertTrue(environment.getSystemEnvironment().isEmpty()); + SpringProperties.setProperty("spring.getenv.ignore", null); + } + @Test public void getSystemProperties_withAndWithoutSecurityManager() { System.setProperty(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE); @@ -371,7 +385,7 @@ public class StandardEnvironmentTests { // 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)); + String.format("Accessing the system property [%s] is disallowed", DISALLOWED_PROPERTY_NAME)); } } @Override @@ -402,7 +416,8 @@ public class StandardEnvironmentTests { try { systemProperties.get(NON_STRING_PROPERTY_NAME); fail("Expected IllegalArgumentException when searching with non-string key against ReadOnlySystemAttributesMap"); - } catch (IllegalArgumentException ex) { + } + catch (IllegalArgumentException ex) { // expected } } @@ -436,7 +451,7 @@ public class StandardEnvironmentTests { //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)); + String.format("Accessing the system environment variable [%s] is disallowed", DISALLOWED_PROPERTY_NAME)); } } }; @@ -469,7 +484,8 @@ public class StandardEnvironmentTests { if (obj != null && obj.getClass().getName().equals("java.lang.ProcessEnvironment$StringEnvironment")) { return (Map) obj; } - } catch (Exception ex) { + } + catch (Exception ex) { throw new RuntimeException(ex); } } @@ -479,8 +495,9 @@ public class StandardEnvironmentTests { Class processEnvironmentClass; try { processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); - } catch (Exception e) { - throw new RuntimeException(e); + } + catch (Exception ex) { + throw new IllegalStateException(ex); } try { @@ -488,10 +505,12 @@ public class StandardEnvironmentTests { theCaseInsensitiveEnvironmentField.setAccessible(true); Object obj = theCaseInsensitiveEnvironmentField.get(null); return (Map) obj; - } catch (NoSuchFieldException e) { + } + catch (NoSuchFieldException ex) { // do nothing - } catch (Exception e) { - throw new RuntimeException(e); + } + catch (Exception ex) { + throw new IllegalStateException(ex); } try { @@ -499,12 +518,15 @@ public class StandardEnvironmentTests { theEnvironmentField.setAccessible(true); Object obj = theEnvironmentField.get(null); return (Map) obj; - } catch (NoSuchFieldException e) { + } + catch (NoSuchFieldException ex) { // do nothing - } catch (Exception e) { - throw new RuntimeException(e); + } + catch (Exception ex) { + throw new IllegalStateException(ex); } throw new IllegalStateException(); } + }