diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 35aa1a4278b..251ecd2f199 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/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"); * 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 { CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults(); PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName); - TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd); if (pd == null) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "No property '" + propertyName + "' found"); } + TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd); if (td == null) { td = new TypeDescriptor(property(pd)); - cachedIntrospectionResults.putTypeDescriptor(pd, td); + cachedIntrospectionResults.addTypeDescriptor(pd, td); } return convertForProperty(propertyName, null, value, pd, td); } private Object convertForProperty(String propertyName, Object oldValue, Object newValue, PropertyDescriptor pd, TypeDescriptor td) throws TypeMismatchException { + return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), td); } 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 04074816904..c71cd4e1382 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -22,7 +22,6 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.ref.Reference; import java.lang.ref.WeakReference; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -30,10 +29,12 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.SpringProperties; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.ClassUtils; @@ -116,17 +117,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); } @@ -298,16 +289,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() + "]"); @@ -332,7 +326,7 @@ public class CachedIntrospectionResults { this.propertyDescriptorCache.put(pd.getName(), pd); } - this.typeDescriptorCache = new HashMap(); + this.typeDescriptorCache = new ConcurrentHashMap(); } catch (IntrospectionException ex) { throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex); @@ -381,12 +375,12 @@ public class CachedIntrospectionResults { } } - TypeDescriptor getTypeDescriptor(PropertyDescriptor pd) { - return this.typeDescriptorCache.get(pd); + void addTypeDescriptor(PropertyDescriptor pd, TypeDescriptor td) { + this.typeDescriptorCache.put(pd, td); } - void putTypeDescriptor(PropertyDescriptor pd, TypeDescriptor td) { - this.typeDescriptorCache.put(pd, td); + TypeDescriptor getTypeDescriptor(PropertyDescriptor pd) { + return this.typeDescriptorCache.get(pd); } } 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 3671c5e51c9..c2820c29c53 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 691065b7ee3..30e9fc5933c 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; @@ -377,15 +378,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; } @@ -404,15 +405,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); } @Override @@ -424,15 +417,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 70f3effd0b7..aa867709b54 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,18 +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.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.*; @@ -37,6 +36,7 @@ import static org.springframework.core.env.AbstractEnvironment.*; * Unit tests for {@link StandardEnvironment}. * * @author Chris Beams + * @author Juergen Hoeller */ public class StandardEnvironmentTests { @@ -333,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); @@ -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) 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 @@ -401,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 } } @@ -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) 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)); } } }; @@ -468,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); } } @@ -478,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 { @@ -487,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 { @@ -498,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(); } + }