Browse Source

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
pull/445/merge
Juergen Hoeller 12 years ago
parent
commit
8543b91c50
  1. 7
      spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
  2. 46
      spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
  3. 3
      spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java
  4. 130
      spring-core/src/main/java/org/springframework/core/SpringProperties.java
  5. 23
      spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java
  6. 55
      spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java

7
spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -492,20 +492,21 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
public Object convertForProperty(Object value, String propertyName) throws TypeMismatchException { public Object convertForProperty(Object value, String propertyName) throws TypeMismatchException {
CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults(); CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults();
PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName); PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName);
TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
if (pd == null) { if (pd == null) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"No property '" + propertyName + "' found"); "No property '" + propertyName + "' found");
} }
TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
if (td == null) { if (td == null) {
td = new TypeDescriptor(property(pd)); td = new TypeDescriptor(property(pd));
cachedIntrospectionResults.putTypeDescriptor(pd, td); cachedIntrospectionResults.addTypeDescriptor(pd, td);
} }
return convertForProperty(propertyName, null, value, pd, td); return convertForProperty(propertyName, null, value, pd, td);
} }
private Object convertForProperty(String propertyName, Object oldValue, Object newValue, PropertyDescriptor pd, TypeDescriptor td) private Object convertForProperty(String propertyName, Object oldValue, Object newValue, PropertyDescriptor pd, TypeDescriptor td)
throws TypeMismatchException { throws TypeMismatchException {
return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), td); return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), td);
} }

46
spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java

@ -22,7 +22,6 @@ import java.beans.Introspector;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -30,10 +29,12 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.SpringProperties;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -116,17 +117,7 @@ public class CachedIntrospectionResults {
static { static {
boolean ignoreValue; shouldIntrospectorIgnoreBeaninfoClasses = SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
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;
} }
@ -298,16 +289,19 @@ public class CachedIntrospectionResults {
} }
this.beanInfo = beanInfo; this.beanInfo = beanInfo;
// Immediately remove class from Introspector cache, to allow for proper // Only bother with flushFromCaches if the Introspector actually cached...
// garbage collection on class loader shutdown - we cache it here anyway, if (!shouldIntrospectorIgnoreBeaninfoClasses) {
// in a GC-friendly manner. In contrast to CachedIntrospectionResults, // Immediately remove class from Introspector cache, to allow for proper
// Introspector does not use WeakReferences as values of its WeakHashMap! // garbage collection on class loader shutdown - we cache it here anyway,
Class<?> classToFlush = beanClass; // in a GC-friendly manner. In contrast to CachedIntrospectionResults,
do { // Introspector does not use WeakReferences as values of its WeakHashMap!
Introspector.flushFromCaches(classToFlush); Class<?> classToFlush = beanClass;
classToFlush = classToFlush.getSuperclass(); do {
Introspector.flushFromCaches(classToFlush);
classToFlush = classToFlush.getSuperclass();
}
while (classToFlush != null);
} }
while (classToFlush != null);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]"); logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
@ -332,7 +326,7 @@ public class CachedIntrospectionResults {
this.propertyDescriptorCache.put(pd.getName(), pd); this.propertyDescriptorCache.put(pd.getName(), pd);
} }
this.typeDescriptorCache = new HashMap<PropertyDescriptor, TypeDescriptor>(); this.typeDescriptorCache = new ConcurrentHashMap<PropertyDescriptor, TypeDescriptor>();
} }
catch (IntrospectionException ex) { catch (IntrospectionException ex) {
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex); throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
@ -381,12 +375,12 @@ public class CachedIntrospectionResults {
} }
} }
TypeDescriptor getTypeDescriptor(PropertyDescriptor pd) { void addTypeDescriptor(PropertyDescriptor pd, TypeDescriptor td) {
return this.typeDescriptorCache.get(pd); this.typeDescriptorCache.put(pd, td);
} }
void putTypeDescriptor(PropertyDescriptor pd, TypeDescriptor td) { TypeDescriptor getTypeDescriptor(PropertyDescriptor pd) {
this.typeDescriptorCache.put(pd, td); return this.typeDescriptorCache.get(pd);
} }
} }

3
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.beans.BeansException;
import org.springframework.core.Constants; import org.springframework.core.Constants;
import org.springframework.core.SpringProperties;
import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.AbstractEnvironment;
import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
@ -84,7 +85,7 @@ public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport
private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK; private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
private boolean searchSystemEnvironment = private boolean searchSystemEnvironment =
!"true".equalsIgnoreCase(System.getProperty(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME)); !SpringProperties.getFlag(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME);
/** /**

130
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.
*
* <p>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.
*
* <p>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));
}
}

23
spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java vendored

@ -25,6 +25,7 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.SpringProperties;
import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -377,15 +378,15 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
catch (AccessControlException ex) { catch (AccessControlException ex) {
return (Map) new ReadOnlySystemAttributesMap() { return (Map) new ReadOnlySystemAttributesMap() {
@Override @Override
protected String getSystemAttribute(String variableName) { protected String getSystemAttribute(String attributeName) {
try { try {
return System.getenv(variableName); return System.getenv(attributeName);
} }
catch (AccessControlException ex) { catch (AccessControlException ex) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info(format("Caught AccessControlException when accessing system " + logger.info(format("Caught AccessControlException when accessing system " +
"environment variable [%s]; its value will be returned [null]. Reason: %s", "environment variable [%s]; its value will be returned [null]. Reason: %s",
variableName, ex.getMessage())); attributeName, ex.getMessage()));
} }
return null; return null;
} }
@ -404,15 +405,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
* returning {@code true} if its value equals "true" in any case. * returning {@code true} if its value equals "true" in any case.
*/ */
protected boolean suppressGetenvAccess() { protected boolean suppressGetenvAccess() {
try { return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
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;
}
} }
@Override @Override
@ -424,15 +417,15 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
catch (AccessControlException ex) { catch (AccessControlException ex) {
return (Map) new ReadOnlySystemAttributesMap() { return (Map) new ReadOnlySystemAttributesMap() {
@Override @Override
protected String getSystemAttribute(String propertyName) { protected String getSystemAttribute(String attributeName) {
try { try {
return System.getProperty(propertyName); return System.getProperty(attributeName);
} }
catch (AccessControlException ex) { catch (AccessControlException ex) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info(format("Caught AccessControlException when accessing system " + logger.info(format("Caught AccessControlException when accessing system " +
"property [%s]; its value will be returned [null]. Reason: %s", "property [%s]; its value will be returned [null]. Reason: %s",
propertyName, ex.getMessage())); attributeName, ex.getMessage()));
} }
return null; return null;
} }

55
spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java vendored

@ -17,18 +17,17 @@
package org.springframework.core.env; package org.springframework.core.env;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.security.Permission; import java.security.Permission;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.SpringProperties;
import org.springframework.mock.env.MockPropertySource; import org.springframework.mock.env.MockPropertySource;
import static java.lang.String.*;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.springframework.core.env.AbstractEnvironment.*; import static org.springframework.core.env.AbstractEnvironment.*;
@ -37,6 +36,7 @@ import static org.springframework.core.env.AbstractEnvironment.*;
* Unit tests for {@link StandardEnvironment}. * Unit tests for {@link StandardEnvironment}.
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller
*/ */
public class StandardEnvironmentTests { public class StandardEnvironmentTests {
@ -333,12 +333,27 @@ public class StandardEnvironmentTests {
try { try {
env.addActiveProfile("invalid-profile"); env.addActiveProfile("invalid-profile");
fail("expected validation exception"); fail("expected validation exception");
} catch (IllegalArgumentException ex) { }
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), assertThat(ex.getMessage(),
equalTo("Invalid profile [invalid-profile]: must not contain dash character")); 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 @Test
public void getSystemProperties_withAndWithoutSecurityManager() { public void getSystemProperties_withAndWithoutSecurityManager() {
System.setProperty(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE); System.setProperty(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
@ -370,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) // 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)) { if (DISALLOWED_PROPERTY_NAME.equals(key)) {
throw new AccessControlException( 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 @Override
@ -401,7 +416,8 @@ public class StandardEnvironmentTests {
try { try {
systemProperties.get(NON_STRING_PROPERTY_NAME); systemProperties.get(NON_STRING_PROPERTY_NAME);
fail("Expected IllegalArgumentException when searching with non-string key against ReadOnlySystemAttributesMap"); fail("Expected IllegalArgumentException when searching with non-string key against ReadOnlySystemAttributesMap");
} catch (IllegalArgumentException ex) { }
catch (IllegalArgumentException ex) {
// expected // expected
} }
} }
@ -435,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) //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())) { if (("getenv."+DISALLOWED_PROPERTY_NAME).equals(perm.getName())) {
throw new AccessControlException( 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));
} }
} }
}; };
@ -468,7 +484,8 @@ public class StandardEnvironmentTests {
if (obj != null && obj.getClass().getName().equals("java.lang.ProcessEnvironment$StringEnvironment")) { if (obj != null && obj.getClass().getName().equals("java.lang.ProcessEnvironment$StringEnvironment")) {
return (Map<String, String>) obj; return (Map<String, String>) obj;
} }
} catch (Exception ex) { }
catch (Exception ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
} }
@ -478,8 +495,9 @@ public class StandardEnvironmentTests {
Class<?> processEnvironmentClass; Class<?> processEnvironmentClass;
try { try {
processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
} catch (Exception e) { }
throw new RuntimeException(e); catch (Exception ex) {
throw new IllegalStateException(ex);
} }
try { try {
@ -487,10 +505,12 @@ public class StandardEnvironmentTests {
theCaseInsensitiveEnvironmentField.setAccessible(true); theCaseInsensitiveEnvironmentField.setAccessible(true);
Object obj = theCaseInsensitiveEnvironmentField.get(null); Object obj = theCaseInsensitiveEnvironmentField.get(null);
return (Map<String, String>) obj; return (Map<String, String>) obj;
} catch (NoSuchFieldException e) { }
catch (NoSuchFieldException ex) {
// do nothing // do nothing
} catch (Exception e) { }
throw new RuntimeException(e); catch (Exception ex) {
throw new IllegalStateException(ex);
} }
try { try {
@ -498,12 +518,15 @@ public class StandardEnvironmentTests {
theEnvironmentField.setAccessible(true); theEnvironmentField.setAccessible(true);
Object obj = theEnvironmentField.get(null); Object obj = theEnvironmentField.get(null);
return (Map<String, String>) obj; return (Map<String, String>) obj;
} catch (NoSuchFieldException e) { }
catch (NoSuchFieldException ex) {
// do nothing // do nothing
} catch (Exception e) { }
throw new RuntimeException(e); catch (Exception ex) {
throw new IllegalStateException(ex);
} }
throw new IllegalStateException(); throw new IllegalStateException();
} }
} }

Loading…
Cancel
Save