diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java index bfbe24aad5..89a97883c2 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java @@ -26,10 +26,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; -import java.nio.charset.Charset; -import java.nio.file.Files; import java.nio.file.NoSuchFileException; -import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.jar.JarEntry; @@ -323,39 +320,6 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { protected void customizeConnection(HttpURLConnection con) throws IOException { } - /** - * This implementation returns the contents of a file as a string using the - * system default Charset. Provided the resource exists and the context has - * access to it, the contents will be returned as a single string with line - * feed characters retained. - * @return the contents of the requested file as a {@code String}. - * @throws FileNotFoundException in the event the file path is invalid. - * @throws IOException if the file can not be read or cannot be serialzied. - */ - @Override - public String getContentAsString() throws IOException { - - if( !exists() ) { - throw new FileNotFoundException(getDescription() + " cannot be found."); - } - if ( !isReadable() ) { - throw new IOException(getDescription() + " cannot be opened for reading."); - } - return new String(Files.readAllBytes(Paths.get(getFile().getAbsolutePath())), Charset.defaultCharset()); - } - - @Override - public String getContentAsString(Charset charset) throws IOException { - - if( !exists() ) { - throw new FileNotFoundException(getDescription() + " cannot be found."); - } - if ( !isReadable() ) { - throw new IOException(getDescription() + " cannot be opened for reading."); - } - return new String(Files.readAllBytes(Paths.get(getFile().getAbsolutePath())), charset); - - } /** * Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime. diff --git a/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java b/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java index c5989b8330..6e38d88812 100644 --- a/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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.core.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Arrays; import org.springframework.lang.Nullable; @@ -100,6 +101,19 @@ public class ByteArrayResource extends AbstractResource { return new ByteArrayInputStream(this.byteArray); } + @Override + public byte[] getContentAsByteArray() throws IOException { + int length = this.byteArray.length; + byte[] result = new byte[length]; + System.arraycopy(this.byteArray, 0, result, 0, length); + return result; + } + + @Override + public String getContentAsString(Charset charset) throws IOException { + return new String(this.byteArray, charset); + } + /** * This implementation returns a description that includes the passed-in * {@code description}, if any. diff --git a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java index b5fda0a575..3db024f0c4 100644 --- a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -27,6 +27,7 @@ import java.net.URL; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -196,6 +197,26 @@ public class FileSystemResource extends AbstractResource implements WritableReso } } + @Override + public byte[] getContentAsByteArray() throws IOException { + try { + return Files.readAllBytes(this.filePath); + } + catch (NoSuchFileException ex) { + throw new FileNotFoundException(ex.getMessage()); + } + } + + @Override + public String getContentAsString(Charset charset) throws IOException { + try { + return Files.readString(this.filePath, charset); + } + catch (NoSuchFileException ex) { + throw new FileNotFoundException(ex.getMessage()); + } + } + /** * This implementation checks whether the underlying file is marked as writable * (and corresponds to an actual file with content, not to a directory). diff --git a/spring-core/src/main/java/org/springframework/core/io/PathResource.java b/spring-core/src/main/java/org/springframework/core/io/PathResource.java index 6412f001a2..53debee25d 100644 --- a/spring-core/src/main/java/org/springframework/core/io/PathResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/PathResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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,6 +25,7 @@ import java.net.URI; import java.net.URL; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.OpenOption; @@ -141,6 +142,26 @@ public class PathResource extends AbstractResource implements WritableResource { return Files.newInputStream(this.path); } + @Override + public byte[] getContentAsByteArray() throws IOException { + try { + return Files.readAllBytes(this.path); + } + catch (NoSuchFileException ex) { + throw new FileNotFoundException(ex.getMessage()); + } + } + + @Override + public String getContentAsString(Charset charset) throws IOException { + try { + return Files.readString(this.path, charset); + } + catch (NoSuchFileException ex) { + throw new FileNotFoundException(ex.getMessage()); + } + } + /** * This implementation checks whether the underlying file is marked as writable * (and corresponds to an actual file with content, not to a directory). diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java index 0d665d98f0..d652a3fb66 100644 --- a/spring-core/src/main/java/org/springframework/core/io/Resource.java +++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -17,9 +17,9 @@ package org.springframework.core.io; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URI; import java.net.URL; import java.nio.channels.Channels; @@ -27,6 +27,7 @@ import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; import org.springframework.lang.Nullable; +import org.springframework.util.FileCopyUtils; /** * Interface for a resource descriptor that abstracts from the actual @@ -179,40 +180,29 @@ public interface Resource extends InputStreamSource { String getDescription(); /** - * Return a {@link ReadableByteChannel}. - *

It is expected that each call creates a fresh channel. - *

The default implementation returns {@link Channels#newChannel(InputStream)} - * with the result of {@link #getInputStream()}. - * @return the byte channel for the underlying resource (must not be {@code null}) - * @throws java.io.FileNotFoundException if the underlying resource doesn't exist - * @throws IOException if the content channel could not be opened - * @since 5.0 - * @see #getInputStream() + * Return the contents of this resource as a byte array. + * @return the contents of this resource as byte array + * @throws java.io.FileNotFoundException if the resource cannot be resolved as + * absolute file path, i.e. if the resource is not available in a file system + * @throws IOException in case of general resolution/reading failures + * @since 6.0.5 */ - - /** - * Returns the contents of a file as a string using the system default Charset. - *

The default implementation returns a {@link Object#toString()} representation of the resource. - * @return the contents of the requested file as a {@code String}. - * @throws FileNotFoundException in the event the file path is invalid. - * @throws IOException if the file can not be read or cannot be accessed. - * @since 5.2.5 - */ - default String getContentAsString() throws IOException{ - return toString(); + default byte[] getContentAsByteArray() throws IOException { + return FileCopyUtils.copyToByteArray(getInputStream()); } /** - * Returns the contents of a file as a string using the specified Charset. - *

The default implementation returns a {@link Object#toString()} representation of the resource. - * @param charset the {@code Charset} to use to deserialize the content. Defaults to system default. - * @return the contents of the requested file as a {@code String}. - * @throws FileNotFoundException in the event the file path is invalid. - * @throws IOException if the file can not be read or cannot be accessed. - * @since 5.2.5 + * Returns the contents of this resource as a string, using the specified + * charset. + * @param charset the charset to use for decoding + * @return the contents of this resource as a {@code String} + * @throws java.io.FileNotFoundException if the resource cannot be resolved as + * absolute file path, i.e. if the resource is not available in a file system + * @throws IOException in case of general resolution/reading failures + * @since 6.0.5 */ - default String getContentAsString(Charset charset) throws IOException{ - return toString(); + default String getContentAsString(Charset charset) throws IOException { + return FileCopyUtils.copyToString(new InputStreamReader(getInputStream(), charset)); } } 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 ed4eb169da..2d511515cc 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -159,6 +159,27 @@ public class EncodedResource implements InputStreamSource { return this.resource.getInputStream(); } + /** + * Returns the contents of the specified resource as a string, using the specified + * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding} + * (if any). + * @throws IOException if opening the resource failed + * @since 6.0.5 + */ + public String getContentAsString() throws IOException { + Charset charset; + if (this.charset != null) { + charset = this.charset; + } + else if (this.encoding != null) { + charset = Charset.forName(this.encoding); + } + else { + charset = Charset.defaultCharset(); + } + return this.resource.getContentAsString(charset); + } + @Override public boolean equals(@Nullable Object other) { diff --git a/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java b/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java index 70d7dbd534..7e4312c126 100644 --- a/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -29,6 +29,8 @@ import java.net.URISyntaxException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Stream; @@ -49,6 +51,7 @@ import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Unit tests for various {@link Resource} implementations. @@ -69,6 +72,7 @@ class ResourceTests { assertThat(resource.isReadable()).isTrue(); assertThat(resource.contentLength() > 0).isTrue(); assertThat(resource.lastModified() > 0).isTrue(); + assertThat(resource.getContentAsByteArray()).containsExactly(Files.readAllBytes(Path.of(resource.getURI()))); } @ParameterizedTest(name = "{index}: {0}") @@ -113,24 +117,13 @@ class ResourceTests { Resource relative4 = resource.createRelative("X.class"); assertThat(relative4.exists()).isFalse(); assertThat(relative4.isReadable()).isFalse(); + assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::contentLength); + assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::lastModified); + assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::getInputStream); + assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::readableChannel); + assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::getContentAsByteArray); assertThatExceptionOfType(FileNotFoundException.class).isThrownBy( - relative4::contentLength); - assertThatExceptionOfType(FileNotFoundException.class).isThrownBy( - relative4::lastModified); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("resource") - void loadingMissingResourceFails(Resource resource) { - assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> - resource.createRelative("X").getInputStream()); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("resource") - void readingMissingResourceFails(Resource resource) { - assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> - resource.createRelative("X").readableChannel()); + () -> relative4.getContentAsString(StandardCharsets.UTF_8)); } private static Stream resource() throws URISyntaxException { @@ -153,12 +146,18 @@ class ResourceTests { @Test void hasContent() throws Exception { - Resource resource = new ByteArrayResource("testString".getBytes()); + String testString = "testString"; + byte[] testBytes = testString.getBytes(); + Resource resource = new ByteArrayResource(testBytes); assertThat(resource.exists()).isTrue(); assertThat(resource.isOpen()).isFalse(); - String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream())); - assertThat(content).isEqualTo("testString"); - assertThat(new ByteArrayResource("testString".getBytes())).isEqualTo(resource); + byte[] contentBytes = resource.getContentAsByteArray(); + assertThat(contentBytes).containsExactly(testBytes); + String contentString = resource.getContentAsString(StandardCharsets.US_ASCII); + assertThat(contentString).isEqualTo(testString); + contentString = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream())); + assertThat(contentString).isEqualTo(testString); + assertThat(new ByteArrayResource(testBytes)).isEqualTo(resource); } @Test @@ -181,11 +180,22 @@ class ResourceTests { @Test void hasContent() throws Exception { - InputStream is = new ByteArrayInputStream("testString".getBytes()); - Resource resource = new InputStreamResource(is); - String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream())); - assertThat(content).isEqualTo("testString"); - assertThat(new InputStreamResource(is)).isEqualTo(resource); + String testString = "testString"; + byte[] testBytes = testString.getBytes(); + InputStream is = new ByteArrayInputStream(testBytes); + Resource resource1 = new InputStreamResource(is); + String content = FileCopyUtils.copyToString(new InputStreamReader(resource1.getInputStream())); + assertThat(content).isEqualTo(testString); + assertThat(new InputStreamResource(is)).isEqualTo(resource1); + assertThatIllegalStateException().isThrownBy(resource1::getInputStream); + + Resource resource2 = new InputStreamResource(new ByteArrayInputStream(testBytes)); + assertThat(resource2.getContentAsByteArray()).containsExactly(testBytes); + assertThatIllegalStateException().isThrownBy(resource2::getContentAsByteArray); + + Resource resource3 = new InputStreamResource(new ByteArrayInputStream(testBytes)); + assertThat(resource3.getContentAsString(StandardCharsets.US_ASCII)).isEqualTo(testString); + assertThatIllegalStateException().isThrownBy(() -> resource3.getContentAsString(StandardCharsets.US_ASCII)); } @Test @@ -435,6 +445,9 @@ class ResourceTests { .withMessageContaining(name); assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> resource.createRelative("/testing")).withMessageContaining(name); + assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(resource::getContentAsByteArray); + assertThatExceptionOfType(FileNotFoundException.class).isThrownBy( + () -> resource.getContentAsString(StandardCharsets.US_ASCII)); assertThat(resource.getFilename()).isNull(); } @@ -456,25 +469,4 @@ class ResourceTests { } - @Test - void getContentAsString_givenValidFile_ShouldReturnFileContent() throws IOException { - String expectedString = "\n" - + "\n" - + "\n" - + "\tbar\n" - + ""; - - String fileDirString = - new ClassPathResource("org/springframework/core/io/example.xml").getContentAsString(); - assertThat(fileDirString).isNotBlank(); - assertThat(fileDirString).isEqualTo(expectedString); - } - - @Test - void getContentAsString_givenAnInvalidFile_ShouldThrowFileNotFoundException(){ - assertThatExceptionOfType(FileNotFoundException.class) - .isThrownBy(new ClassPathResource("nonExistantFile")::getContentAsString); - - } - } diff --git a/spring-core/src/test/resources/org/springframework/core/io/example.xml b/spring-core/src/test/resources/org/springframework/core/io/example.xml index b9d4450840..1d638537e1 100644 --- a/spring-core/src/test/resources/org/springframework/core/io/example.xml +++ b/spring-core/src/test/resources/org/springframework/core/io/example.xml @@ -2,4 +2,5 @@ bar - \ No newline at end of file + + diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java index 5750df7148..14db7243de 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -269,6 +270,16 @@ public class EncodedResourceResolver extends AbstractResourceResolver { return this.encoded.getDescription(); } + @Override + public byte[] getContentAsByteArray() throws IOException { + return this.encoded.getContentAsByteArray(); + } + + @Override + public String getContentAsString(Charset charset) throws IOException { + return this.encoded.getContentAsString(charset); + } + @Override public HttpHeaders getResponseHeaders() { HttpHeaders headers; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/VersionResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/VersionResourceResolver.java index 013358f76d..a93264cb23 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/VersionResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/VersionResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -308,6 +309,16 @@ public class VersionResourceResolver extends AbstractResourceResolver { return this.original.getDescription(); } + @Override + public byte[] getContentAsByteArray() throws IOException { + return this.original.getContentAsByteArray(); + } + + @Override + public String getContentAsString(Charset charset) throws IOException { + return this.original.getContentAsString(charset); + } + @Override public InputStream getInputStream() throws IOException { return this.original.getInputStream(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java index 4d0a6af733..98d30f07c7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/EncodedResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -266,6 +267,16 @@ public class EncodedResourceResolver extends AbstractResourceResolver { return this.encoded.getDescription(); } + @Override + public byte[] getContentAsByteArray() throws IOException { + return this.encoded.getContentAsByteArray(); + } + + @Override + public String getContentAsString(Charset charset) throws IOException { + return this.encoded.getContentAsString(charset); + } + @Override public HttpHeaders getResponseHeaders() { HttpHeaders headers; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java index 9010e79c12..96d1d48507 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -304,6 +305,16 @@ public class VersionResourceResolver extends AbstractResourceResolver { return this.original.getDescription(); } + @Override + public byte[] getContentAsByteArray() throws IOException { + return this.original.getContentAsByteArray(); + } + + @Override + public String getContentAsString(Charset charset) throws IOException { + return this.original.getContentAsString(charset); + } + @Override public InputStream getInputStream() throws IOException { return this.original.getInputStream();