From 2c637dcb2ed847c4919dd979894da38ca6ee7cbb Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 1 Apr 2015 17:02:55 +0200 Subject: [PATCH] ReaderEditor supports Reader injection analogous to InputStreamEditor (from Spring resource location) Also, EncodedResource implements InputStreamSource now since it declares getInputStream() anyway. Issue: SPR-12876 --- .../beans/PropertyEditorRegistrySupport.java | 5 +- .../propertyeditors/InputStreamEditor.java | 11 ++- .../beans/propertyeditors/ReaderEditor.java | 89 +++++++++++++++++++ .../support/ResourceEditorRegistrar.java | 6 +- .../InputStreamEditorTests.java | 17 ++-- .../propertyeditors/ReaderEditorTests.java | 78 ++++++++++++++++ .../ApplicationContextExpressionTests.java | 55 ++++++++++++ .../core/io/support/EncodedResource.java | 5 +- 8 files changed, 249 insertions(+), 17 deletions(-) create mode 100644 spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java create mode 100644 spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java index 939f81a6acb..f42d6c332ec 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -19,6 +19,7 @@ package org.springframework.beans; import java.beans.PropertyEditor; import java.io.File; import java.io.InputStream; +import java.io.Reader; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; @@ -60,6 +61,7 @@ import org.springframework.beans.propertyeditors.InputStreamEditor; import org.springframework.beans.propertyeditors.LocaleEditor; import org.springframework.beans.propertyeditors.PatternEditor; import org.springframework.beans.propertyeditors.PropertiesEditor; +import org.springframework.beans.propertyeditors.ReaderEditor; import org.springframework.beans.propertyeditors.StringArrayPropertyEditor; import org.springframework.beans.propertyeditors.TimeZoneEditor; import org.springframework.beans.propertyeditors.URIEditor; @@ -211,6 +213,7 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { this.defaultEditors.put(Locale.class, new LocaleEditor()); this.defaultEditors.put(Pattern.class, new PatternEditor()); this.defaultEditors.put(Properties.class, new PropertiesEditor()); + this.defaultEditors.put(Reader.class, new ReaderEditor()); this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor()); this.defaultEditors.put(TimeZone.class, new TimeZoneEditor()); this.defaultEditors.put(URI.class, new URIEditor()); diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java index 220f381e88e..f982e0af170 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -25,13 +25,13 @@ import org.springframework.util.Assert; /** * One-way PropertyEditor which can convert from a text String to a - * {@code java.io.InputStream}, interpreting the given String - * as Spring resource location (e.g. a URL String). + * {@code java.io.InputStream}, interpreting the given String as a + * Spring resource location (e.g. a URL String). * *

Supports Spring-style URL notation: any fully qualified standard URL * ("file:", "http:", etc) and Spring's special "classpath:" pseudo-URL. * - *

Note that in the default usage, the stream is not closed by Spring itself! + *

Note that such streams do usually not get closed by Spring itself! * * @author Juergen Hoeller * @since 1.0.1 @@ -73,8 +73,7 @@ public class InputStreamEditor extends PropertyEditorSupport { setValue(resource != null ? resource.getInputStream() : null); } catch (IOException ex) { - throw new IllegalArgumentException( - "Could not retrieve InputStream for " + resource + ": " + ex.getMessage()); + throw new IllegalArgumentException("Failed to retrieve InputStream for " + resource, ex); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java new file mode 100644 index 00000000000..e0ccaf820ad --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2015 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.beans.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.io.IOException; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceEditor; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.util.Assert; + +/** + * One-way PropertyEditor which can convert from a text String to a + * {@code java.io.Reader}, interpreting the given String as a Spring + * resource location (e.g. a URL String). + * + *

Supports Spring-style URL notation: any fully qualified standard URL + * ("file:", "http:", etc) and Spring's special "classpath:" pseudo-URL. + * + *

Note that such readers do usually not get closed by Spring itself! + * + * @author Juergen Hoeller + * @since 4.2 + * @see java.io.Reader + * @see org.springframework.core.io.ResourceEditor + * @see org.springframework.core.io.ResourceLoader + * @see InputStreamEditor + */ +public class ReaderEditor extends PropertyEditorSupport { + + private final ResourceEditor resourceEditor; + + + /** + * Create a new ReaderEditor, + * using the default ResourceEditor underneath. + */ + public ReaderEditor() { + this.resourceEditor = new ResourceEditor(); + } + + /** + * Create a new ReaderEditor, + * using the given ResourceEditor underneath. + * @param resourceEditor the ResourceEditor to use + */ + public ReaderEditor(ResourceEditor resourceEditor) { + Assert.notNull(resourceEditor, "ResourceEditor must not be null"); + this.resourceEditor = resourceEditor; + } + + + @Override + public void setAsText(String text) throws IllegalArgumentException { + this.resourceEditor.setAsText(text); + Resource resource = (Resource) this.resourceEditor.getValue(); + try { + setValue(resource != null ? new EncodedResource(resource).getReader() : null); + } + catch (IOException ex) { + throw new IllegalArgumentException("Failed to retrieve Reader for " + resource, ex); + } + } + + /** + * This implementation returns {@code null} to indicate that + * there is no appropriate text representation. + */ + @Override + public String getAsText() { + return null; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java index 2214f14fea4..06d2ee5d688 100644 --- a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java +++ b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -19,6 +19,7 @@ package org.springframework.beans.support; import java.beans.PropertyEditor; import java.io.File; import java.io.InputStream; +import java.io.Reader; import java.net.URI; import java.net.URL; @@ -32,10 +33,10 @@ import org.springframework.beans.propertyeditors.ClassEditor; import org.springframework.beans.propertyeditors.FileEditor; import org.springframework.beans.propertyeditors.InputSourceEditor; import org.springframework.beans.propertyeditors.InputStreamEditor; +import org.springframework.beans.propertyeditors.ReaderEditor; import org.springframework.beans.propertyeditors.URIEditor; import org.springframework.beans.propertyeditors.URLEditor; import org.springframework.core.env.PropertyResolver; -import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ContextResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceEditor; @@ -102,6 +103,7 @@ public class ResourceEditorRegistrar implements PropertyEditorRegistrar { doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor)); doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor)); doRegisterEditor(registry, File.class, new FileEditor(baseEditor)); + doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor)); doRegisterEditor(registry, URL.class, new URLEditor(baseEditor)); ClassLoader classLoader = this.resourceLoader.getClassLoader(); diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java index 537be76543d..5073e5d5570 100644 --- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/InputStreamEditorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2015 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. @@ -30,9 +30,9 @@ import static org.junit.Assert.*; * @author Rick Evans * @author Chris Beams */ -public final class InputStreamEditorTests { +public class InputStreamEditorTests { - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testCtorWithNullResourceEditor() throws Exception { new InputStreamEditor(null); } @@ -41,7 +41,8 @@ public final class InputStreamEditorTests { public void testSunnyDay() throws Exception { InputStream stream = null; try { - String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/" + ClassUtils.getShortName(getClass()) + ".class"; + String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + + "/" + ClassUtils.getShortName(getClass()) + ".class"; InputStreamEditor editor = new InputStreamEditor(); editor.setAsText(resource); Object value = editor.getValue(); @@ -49,14 +50,15 @@ public final class InputStreamEditorTests { assertTrue(value instanceof InputStream); stream = (InputStream) value; assertTrue(stream.available() > 0); - } finally { + } + finally { if (stream != null) { stream.close(); } } } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testWhenResourceDoesNotExist() throws Exception { String resource = "classpath:bingo!"; InputStreamEditor editor = new InputStreamEditor(); @@ -66,7 +68,8 @@ public final class InputStreamEditorTests { @Test public void testGetAsTextReturnsNullByDefault() throws Exception { assertNull(new InputStreamEditor().getAsText()); - String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/" + ClassUtils.getShortName(getClass()) + ".class"; + String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + + "/" + ClassUtils.getShortName(getClass()) + ".class"; InputStreamEditor editor = new InputStreamEditor(); editor.setAsText(resource); assertNull(editor.getAsText()); diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java new file mode 100644 index 00000000000..2729922ad9c --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ReaderEditorTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2015 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.beans.propertyeditors; + +import java.io.Reader; + +import org.junit.Test; + +import org.springframework.util.ClassUtils; + +import static org.junit.Assert.*; + +/** + * Unit tests for the {@link ReaderEditor} class. + * + * @author Juergen Hoeller + * @since 4.2 + */ +public class ReaderEditorTests { + + @Test(expected = IllegalArgumentException.class) + public void testCtorWithNullResourceEditor() throws Exception { + new InputStreamEditor(null); + } + + @Test + public void testSunnyDay() throws Exception { + Reader reader = null; + try { + String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + + "/" + ClassUtils.getShortName(getClass()) + ".class"; + ReaderEditor editor = new ReaderEditor(); + editor.setAsText(resource); + Object value = editor.getValue(); + assertNotNull(value); + assertTrue(value instanceof Reader); + reader = (Reader) value; + assertTrue(reader.ready()); + } + finally { + if (reader != null) { + reader.close(); + } + } + } + + @Test(expected = IllegalArgumentException.class) + public void testWhenResourceDoesNotExist() throws Exception { + String resource = "classpath:bingo!"; + ReaderEditor editor = new ReaderEditor(); + editor.setAsText(resource); + } + + @Test + public void testGetAsTextReturnsNullByDefault() throws Exception { + assertNull(new ReaderEditor().getAsText()); + String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + + "/" + ClassUtils.getShortName(getClass()) + ".class"; + ReaderEditor editor = new ReaderEditor(); + editor.setAsText(resource); + assertNull(editor.getAsText()); + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java b/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java index 7fb41c022e9..f744427a926 100644 --- a/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java +++ b/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java @@ -16,7 +16,13 @@ package org.springframework.context.expression; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; import java.io.Serializable; +import java.net.URI; +import java.net.URL; import java.security.AccessControlException; import java.security.Permission; import java.util.Properties; @@ -36,13 +42,18 @@ import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.EncodedResource; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; import org.springframework.tests.sample.beans.TestBean; +import org.springframework.util.FileCopyUtils; import org.springframework.util.SerializationTestUtils; import org.springframework.util.StopWatch; @@ -56,6 +67,7 @@ public class ApplicationContextExpressionTests { private static final Log factoryLog = LogFactory.getLog(DefaultListableBeanFactory.class); + @Test public void genericApplicationContext() throws Exception { GenericApplicationContext ac = new GenericApplicationContext(); @@ -312,6 +324,27 @@ public class ApplicationContextExpressionTests { assertTrue(str.startsWith("test-")); } + @Test + public void resourceInjection() throws IOException { + System.setProperty("logfile", "log4j.properties"); + try { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ResourceInjectionBean.class); + ResourceInjectionBean resourceInjectionBean = ac.getBean(ResourceInjectionBean.class); + Resource resource = new ClassPathResource("log4j.properties"); + assertEquals(resource, resourceInjectionBean.resource); + assertEquals(resource.getURL(), resourceInjectionBean.url); + assertEquals(resource.getURI(), resourceInjectionBean.uri); + assertEquals(resource.getFile(), resourceInjectionBean.file); + assertArrayEquals(FileCopyUtils.copyToByteArray(resource.getInputStream()), + FileCopyUtils.copyToByteArray(resourceInjectionBean.inputStream)); + assertEquals(FileCopyUtils.copyToString(new EncodedResource(resource).getReader()), + FileCopyUtils.copyToString(resourceInjectionBean.reader)); + } + finally { + System.getProperties().remove("logfile"); + } + } + @SuppressWarnings("serial") public static class ValueTestBean implements Serializable { @@ -450,4 +483,26 @@ public class ApplicationContextExpressionTests { } } + + public static class ResourceInjectionBean { + + @Value("classpath:#{systemProperties.logfile}") + Resource resource; + + @Value("classpath:#{systemProperties.logfile}") + URL url; + + @Value("classpath:#{systemProperties.logfile}") + URI uri; + + @Value("classpath:#{systemProperties.logfile}") + File file; + + @Value("classpath:#{systemProperties.logfile}") + InputStream inputStream; + + @Value("classpath:#{systemProperties.logfile}") + Reader reader; + } + } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java index 62db53999df..edb10b0b9d5 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java @@ -22,6 +22,7 @@ import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; +import org.springframework.core.io.InputStreamSource; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -39,7 +40,7 @@ import org.springframework.util.ObjectUtils; * @see java.io.Reader * @see java.nio.charset.Charset */ -public class EncodedResource { +public class EncodedResource implements InputStreamSource { private final Resource resource; @@ -85,6 +86,7 @@ public class EncodedResource { this.charset = charset; } + /** * Return the {@code Resource} held by this {@code EncodedResource}. */ @@ -146,6 +148,7 @@ public class EncodedResource { * @see #requiresReader() * @see #getReader() */ + @Override public InputStream getInputStream() throws IOException { return this.resource.getInputStream(); }