diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index cf5143fed07..ddd70b27086 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -196,6 +196,10 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { return this.propertyResolver.getProperty(key, targetType); } + public Class getPropertyAsClass(String key, Class targetType) { + return this.propertyResolver.getPropertyAsClass(key, targetType); + } + public String getRequiredProperty(String key) throws IllegalStateException { return this.propertyResolver.getRequiredProperty(key); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/PropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertyResolver.java index cf3fc60b340..9f1c5bfee0b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/PropertyResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertyResolver.java @@ -41,6 +41,15 @@ public interface PropertyResolver { */ T getProperty(String key, Class targetType); + /** + * Convert the property value associated with the given key to a {@code Class} + * of type {@code T} or {@code null} if the key cannot be resolved. + * @throws ConversionException if class specified by property value cannot be found + * or loaded or if targetType is not assignable from class specified by property value + * @see #getProperty(String, Class) + */ + Class getPropertyAsClass(String key, Class targetType); + /** * Return the property value associated with the given key, converted to the given * targetType (never {@code null}). diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java index 5443e340094..53cd4936c42 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java @@ -18,6 +18,9 @@ package org.springframework.core.env; import static java.lang.String.format; +import org.springframework.core.convert.ConversionException; +import org.springframework.util.ClassUtils; + /** * {@link PropertyResolver} implementation that resolves property values against * an underlying set of {@link PropertySources}. @@ -58,7 +61,7 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver { if (logger.isTraceEnabled()) { logger.trace(format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName())); } - + for (PropertySource propertySource : this.propertySources) { if (debugEnabled) { logger.debug(format("Searching for key '%s' in [%s]", key, propertySource.getName())); @@ -79,11 +82,68 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver { return conversionService.convert(value, targetValueType); } } - + if (debugEnabled) { logger.debug(format("Could not find key '%s' in any property source. Returning [null]", key)); } return null; } + public Class getPropertyAsClass(String key, Class targetValueType) { + boolean debugEnabled = logger.isDebugEnabled(); + if (logger.isTraceEnabled()) { + logger.trace(format("getPropertyAsClass(\"%s\", %s)", key, targetValueType.getSimpleName())); + } + + for (PropertySource propertySource : this.propertySources) { + if (debugEnabled) { + logger.debug(format("Searching for key '%s' in [%s]", key, propertySource.getName())); + } + Object value; + if ((value = propertySource.getProperty(key)) != null) { + if (debugEnabled) { + logger.debug( + format("Found key '%s' in [%s] with value '%s'", key, propertySource.getName(), value)); + } + + Class clazz; + if (value instanceof String) { + try { + clazz = ClassUtils.forName((String)value, null); + } catch (Exception ex) { + throw new ClassConversionException((String)value, targetValueType, ex); + } + } + else if (value instanceof Class) { + clazz = (Class)value; + } else { + clazz = value.getClass(); + } + + if (!targetValueType.isAssignableFrom(clazz)) { + throw new ClassConversionException(clazz, targetValueType); + } + @SuppressWarnings("unchecked") + Class targetClass = (Class)clazz; + return targetClass; + } + } + + if (debugEnabled) { + logger.debug(format("Could not find key '%s' in any property source. Returning [null]", key)); + } + return null; + } + + + @SuppressWarnings("serial") + static class ClassConversionException extends ConversionException { + public ClassConversionException(Class actual, Class expected) { + super(String.format("Actual type %s is not assignable to expected type %s", actual.getName(), expected.getName())); + } + + public ClassConversionException(String actual, Class expected, Exception ex) { + super(String.format("Could not find/load class %s during attempt to convert to %s", actual, expected.getName()), ex); + } + } } diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java index 4905ae96a04..4a1c8cd513e 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java @@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.HashMap; @@ -28,6 +29,7 @@ import java.util.Properties; import org.junit.Before; import org.junit.Test; +import org.springframework.core.convert.ConversionException; import org.springframework.mock.env.MockPropertySource; /** @@ -238,4 +240,70 @@ public class PropertySourcesPropertyResolverTests { new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null); } + @Test + public void getPropertyAsClass() throws ClassNotFoundException, LinkageError { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class.getName())); + PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); + assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class)); + } + + @Test + public void getPropertyAsClass_withInterfaceAsTarget() throws ClassNotFoundException, LinkageError { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("some.class", SomeType.class.getName())); + PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); + assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SomeType.class)); + } + + @Test(expected=ConversionException.class) + public void getPropertyAsClass_withMismatchedTypeForValue() { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("some.class", "java.lang.String")); + PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); + resolver.getPropertyAsClass("some.class", SomeType.class); + } + + @Test(expected=ConversionException.class) + public void getPropertyAsClass_withNonExistentClassForValue() { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("some.class", "some.bogus.Class")); + PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); + resolver.getPropertyAsClass("some.class", SomeType.class); + } + + @Test + public void getPropertyAsClass_withObjectForValue() { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("some.class", new SpecificType())); + PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); + assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class)); + } + + @Test(expected=ConversionException.class) + public void getPropertyAsClass_withMismatchedObjectForValue() { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("some.class", new Integer(42))); + PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); + resolver.getPropertyAsClass("some.class", SomeType.class); + } + + @Test + public void getPropertyAsClass_withRealClassForValue() { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class)); + PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); + assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class)); + } + + @Test(expected=ConversionException.class) + public void getPropertyAsClass_withMismatchedRealClassForValue() { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("some.class", Integer.class)); + PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); + resolver.getPropertyAsClass("some.class", SomeType.class); + } + + static interface SomeType { } + static class SpecificType implements SomeType { } }