Merge branch '5.3.x'

This commit is contained in:
Brian Clozel 2022-08-03 23:16:13 +02:00
commit 34266f22c7
4 changed files with 352 additions and 238 deletions

View File

@ -111,6 +111,7 @@ dependencies {
testImplementation("io.projectreactor:reactor-test") testImplementation("io.projectreactor:reactor-test")
testImplementation("io.projectreactor.tools:blockhound") testImplementation("io.projectreactor.tools:blockhound")
testImplementation("org.skyscreamer:jsonassert") testImplementation("org.skyscreamer:jsonassert")
testImplementation("com.squareup.okhttp3:mockwebserver")
testFixturesImplementation("com.google.code.findbugs:jsr305") testFixturesImplementation("com.google.code.findbugs:jsr305")
testFixturesImplementation("org.junit.platform:junit-platform-launcher") testFixturesImplementation("org.junit.platform:junit-platform-launcher")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")

View File

@ -57,6 +57,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
HttpURLConnection httpCon = HttpURLConnection httpCon =
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null); (con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
if (httpCon != null) { if (httpCon != null) {
httpCon.setRequestMethod("HEAD");
int code = httpCon.getResponseCode(); int code = httpCon.getResponseCode();
if (code == HttpURLConnection.HTTP_OK) { if (code == HttpURLConnection.HTTP_OK) {
return true; return true;
@ -107,6 +108,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
URLConnection con = url.openConnection(); URLConnection con = url.openConnection();
customizeConnection(con); customizeConnection(con);
if (con instanceof HttpURLConnection httpCon) { if (con instanceof HttpURLConnection httpCon) {
httpCon.setRequestMethod("HEAD");
int code = httpCon.getResponseCode(); int code = httpCon.getResponseCode();
if (code != HttpURLConnection.HTTP_OK) { if (code != HttpURLConnection.HTTP_OK) {
httpCon.disconnect(); httpCon.disconnect();
@ -244,6 +246,9 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
// Try a URL connection content-length header // Try a URL connection content-length header
URLConnection con = url.openConnection(); URLConnection con = url.openConnection();
customizeConnection(con); customizeConnection(con);
if (con instanceof HttpURLConnection httpCon) {
httpCon.setRequestMethod("HEAD");
}
return con.getContentLengthLong(); return con.getContentLengthLong();
} }
} }
@ -269,6 +274,9 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
// Try a URL connection last-modified header // Try a URL connection last-modified header
URLConnection con = url.openConnection(); URLConnection con = url.openConnection();
customizeConnection(con); customizeConnection(con);
if (con instanceof HttpURLConnection httpCon) {
httpCon.setRequestMethod("HEAD");
}
long lastModified = con.getLastModified(); long lastModified = con.getLastModified();
if (fileCheck && lastModified == 0 && con.getContentLengthLong() <= 0) { if (fileCheck && lastModified == 0 && con.getContentLengthLong() <= 0) {
throw new FileNotFoundException(getDescription() + throw new FileNotFoundException(getDescription() +
@ -278,8 +286,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
} }
/** /**
* Customize the given {@link URLConnection}, obtained in the course of an * Customize the given {@link URLConnection} before fetching the resource.
* {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.
* <p>Calls {@link ResourceUtils#useCachesIfNecessary(URLConnection)} and * <p>Calls {@link ResourceUtils#useCachesIfNecessary(URLConnection)} and
* delegates to {@link #customizeConnection(HttpURLConnection)} if possible. * delegates to {@link #customizeConnection(HttpURLConnection)} if possible.
* Can be overridden in subclasses. * Can be overridden in subclasses.
@ -294,14 +301,12 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
} }
/** /**
* Customize the given {@link HttpURLConnection}, obtained in the course of an * Customize the given {@link HttpURLConnection} before fetching the resource.
* {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call. * <p>Can be overridden in subclasses for configuring request headers and timeouts.
* <p>Sets request method "HEAD" by default. Can be overridden in subclasses.
* @param con the HttpURLConnection to customize * @param con the HttpURLConnection to customize
* @throws IOException if thrown from HttpURLConnection methods * @throws IOException if thrown from HttpURLConnection methods
*/ */
protected void customizeConnection(HttpURLConnection con) throws IOException { protected void customizeConnection(HttpURLConnection con) throws IOException {
con.setRequestMethod("HEAD");
} }

View File

@ -29,7 +29,6 @@ import java.net.URLConnection;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -228,7 +227,7 @@ public class UrlResource extends AbstractFileResolvingResource {
@Override @Override
public InputStream getInputStream() throws IOException { public InputStream getInputStream() throws IOException {
URLConnection con = this.url.openConnection(); URLConnection con = this.url.openConnection();
ResourceUtils.useCachesIfNecessary(con); customizeConnection(con);
try { try {
return con.getInputStream(); return con.getInputStream();
} }

View File

@ -23,20 +23,32 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.HashSet; import java.util.HashSet;
import java.util.stream.Stream;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
/** /**
* Unit tests for various {@link Resource} implementations. * Unit tests for various {@link Resource} implementations.
@ -44,137 +56,25 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue;
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
* @author Sam Brannen * @author Sam Brannen
* @since 09.09.2004 * @author Brian Clozel
*/ */
class ResourceTests { class ResourceTests {
@Test
void byteArrayResource() throws IOException {
Resource resource = new ByteArrayResource("testString".getBytes());
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);
}
@Test @ParameterizedTest(name = "{index}: {0}")
void byteArrayResourceWithDescription() throws IOException { @MethodSource("resource")
Resource resource = new ByteArrayResource("testString".getBytes(), "my description"); void resourceIsValid(Resource resource) throws Exception {
assertThat(resource.exists()).isTrue();
assertThat(resource.isOpen()).isFalse();
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
assertThat(content).isEqualTo("testString");
assertThat(resource.getDescription().contains("my description")).isTrue();
assertThat(new ByteArrayResource("testString".getBytes())).isEqualTo(resource);
}
@Test
void inputStreamResource() throws IOException {
InputStream is = new ByteArrayInputStream("testString".getBytes());
Resource resource = new InputStreamResource(is);
assertThat(resource.exists()).isTrue();
assertThat(resource.isOpen()).isTrue();
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
assertThat(content).isEqualTo("testString");
assertThat(new InputStreamResource(is)).isEqualTo(resource);
}
@Test
void inputStreamResourceWithDescription() throws IOException {
InputStream is = new ByteArrayInputStream("testString".getBytes());
Resource resource = new InputStreamResource(is, "my description");
assertThat(resource.exists()).isTrue();
assertThat(resource.isOpen()).isTrue();
String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
assertThat(content).isEqualTo("testString");
assertThat(resource.getDescription().contains("my description")).isTrue();
assertThat(new InputStreamResource(is)).isEqualTo(resource);
}
@Test
void classPathResource() throws IOException {
Resource resource = new ClassPathResource("org/springframework/core/io/Resource.class");
doTestResource(resource);
Resource resource2 = new ClassPathResource("org/springframework/core/../core/io/./Resource.class");
assertThat(resource2).isEqualTo(resource);
Resource resource3 = new ClassPathResource("org/springframework/core/").createRelative("../core/io/./Resource.class");
assertThat(resource3).isEqualTo(resource);
// Check whether equal/hashCode works in a HashSet.
HashSet<Resource> resources = new HashSet<>();
resources.add(resource);
resources.add(resource2);
assertThat(resources.size()).isEqualTo(1);
}
@Test
void classPathResourceWithClassLoader() throws IOException {
Resource resource =
new ClassPathResource("org/springframework/core/io/Resource.class", getClass().getClassLoader());
doTestResource(resource);
assertThat(new ClassPathResource("org/springframework/core/../core/io/./Resource.class", getClass().getClassLoader())).isEqualTo(resource);
}
@Test
void classPathResourceWithClass() throws IOException {
Resource resource = new ClassPathResource("Resource.class", getClass());
doTestResource(resource);
assertThat(new ClassPathResource("Resource.class", getClass())).isEqualTo(resource);
}
@Test
void fileSystemResource() throws IOException {
String file = getClass().getResource("Resource.class").getFile();
Resource resource = new FileSystemResource(file);
doTestResource(resource);
assertThat(resource).isEqualTo(new FileSystemResource(file));
}
@Test
void fileSystemResourceWithFile() throws IOException {
File file = new File(getClass().getResource("Resource.class").getFile());
Resource resource = new FileSystemResource(file);
doTestResource(resource);
assertThat(resource).isEqualTo(new FileSystemResource(file));
}
@Test
void fileSystemResourceWithFilePath() throws Exception {
Path filePath = Paths.get(getClass().getResource("Resource.class").toURI());
Resource resource = new FileSystemResource(filePath);
doTestResource(resource);
assertThat(resource).isEqualTo(new FileSystemResource(filePath));
}
@Test
void fileSystemResourceWithPlainPath() {
Resource resource = new FileSystemResource("core/io/Resource.class");
assertThat(new FileSystemResource("core/../core/io/./Resource.class")).isEqualTo(resource);
}
@Test
void urlResource() throws IOException {
Resource resource = new UrlResource(getClass().getResource("Resource.class"));
doTestResource(resource);
assertThat(resource).isEqualTo(new UrlResource(getClass().getResource("Resource.class")));
Resource resource2 = new UrlResource("file:core/io/Resource.class");
assertThat(new UrlResource("file:core/../core/io/./Resource.class")).isEqualTo(resource2);
assertThat(new UrlResource("file:/dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(new UrlResource("file:\\dir\\test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(new UrlResource("file:\\dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
}
private void doTestResource(Resource resource) throws IOException {
assertThat(resource.getFilename()).isEqualTo("Resource.class"); assertThat(resource.getFilename()).isEqualTo("Resource.class");
assertThat(resource.getURL().getFile().endsWith("Resource.class")).isTrue(); assertThat(resource.getURL().getFile().endsWith("Resource.class")).isTrue();
assertThat(resource.exists()).isTrue(); assertThat(resource.exists()).isTrue();
assertThat(resource.isReadable()).isTrue(); assertThat(resource.isReadable()).isTrue();
assertThat(resource.contentLength() > 0).isTrue(); assertThat(resource.contentLength() > 0).isTrue();
assertThat(resource.lastModified() > 0).isTrue(); assertThat(resource.lastModified() > 0).isTrue();
}
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("resource")
void resourceCreateRelative(Resource resource) throws Exception {
Resource relative1 = resource.createRelative("ClassPathResource.class"); Resource relative1 = resource.createRelative("ClassPathResource.class");
assertThat(relative1.getFilename()).isEqualTo("ClassPathResource.class"); assertThat(relative1.getFilename()).isEqualTo("ClassPathResource.class");
assertThat(relative1.getURL().getFile().endsWith("ClassPathResource.class")).isTrue(); assertThat(relative1.getURL().getFile().endsWith("ClassPathResource.class")).isTrue();
@ -182,7 +82,11 @@ class ResourceTests {
assertThat(relative1.isReadable()).isTrue(); assertThat(relative1.isReadable()).isTrue();
assertThat(relative1.contentLength() > 0).isTrue(); assertThat(relative1.contentLength() > 0).isTrue();
assertThat(relative1.lastModified() > 0).isTrue(); assertThat(relative1.lastModified() > 0).isTrue();
}
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("resource")
void resourceCreateRelativeWithFolder(Resource resource) throws Exception {
Resource relative2 = resource.createRelative("support/ResourcePatternResolver.class"); Resource relative2 = resource.createRelative("support/ResourcePatternResolver.class");
assertThat(relative2.getFilename()).isEqualTo("ResourcePatternResolver.class"); assertThat(relative2.getFilename()).isEqualTo("ResourcePatternResolver.class");
assertThat(relative2.getURL().getFile().endsWith("ResourcePatternResolver.class")).isTrue(); assertThat(relative2.getURL().getFile().endsWith("ResourcePatternResolver.class")).isTrue();
@ -190,7 +94,11 @@ class ResourceTests {
assertThat(relative2.isReadable()).isTrue(); assertThat(relative2.isReadable()).isTrue();
assertThat(relative2.contentLength() > 0).isTrue(); assertThat(relative2.contentLength() > 0).isTrue();
assertThat(relative2.lastModified() > 0).isTrue(); assertThat(relative2.lastModified() > 0).isTrue();
}
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("resource")
void resourceCreateRelativeWithDotPath(Resource resource) throws Exception {
Resource relative3 = resource.createRelative("../SpringVersion.class"); Resource relative3 = resource.createRelative("../SpringVersion.class");
assertThat(relative3.getFilename()).isEqualTo("SpringVersion.class"); assertThat(relative3.getFilename()).isEqualTo("SpringVersion.class");
assertThat(relative3.getURL().getFile().endsWith("SpringVersion.class")).isTrue(); assertThat(relative3.getURL().getFile().endsWith("SpringVersion.class")).isTrue();
@ -198,7 +106,11 @@ class ResourceTests {
assertThat(relative3.isReadable()).isTrue(); assertThat(relative3.isReadable()).isTrue();
assertThat(relative3.contentLength() > 0).isTrue(); assertThat(relative3.contentLength() > 0).isTrue();
assertThat(relative3.lastModified() > 0).isTrue(); assertThat(relative3.lastModified() > 0).isTrue();
}
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("resource")
void resourceCreateRelativeUnknown(Resource resource) throws Exception {
Resource relative4 = resource.createRelative("X.class"); Resource relative4 = resource.createRelative("X.class");
assertThat(relative4.exists()).isFalse(); assertThat(relative4.exists()).isFalse();
assertThat(relative4.isReadable()).isFalse(); assertThat(relative4.isReadable()).isFalse();
@ -208,141 +120,338 @@ class ResourceTests {
relative4::lastModified); relative4::lastModified);
} }
@Test @ParameterizedTest(name = "{index}: {0}")
void urlResourceFactoryMethods() throws IOException { @MethodSource("resource")
Resource resource1 = new UrlResource("file:core/io/Resource.class"); void loadingMissingResourceFails(Resource resource) {
Resource resource2 = UrlResource.from("file:core/io/Resource.class"); assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
Resource resource3 = UrlResource.from(resource1.getURI()); resource.createRelative("X").getInputStream());
assertThat(resource2.getURL()).isEqualTo(resource1.getURL());
assertThat(resource3.getURL()).isEqualTo(resource1.getURL());
assertThat(UrlResource.from("file:core/../core/io/./Resource.class")).isEqualTo(resource1);
assertThat(UrlResource.from("file:/dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(UrlResource.from("file:\\dir\\test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(UrlResource.from("file:\\dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
} }
@Test @ParameterizedTest(name = "{index}: {0}")
void classPathResourceWithRelativePath() throws IOException { @MethodSource("resource")
Resource resource = new ClassPathResource("dir/"); void readingMissingResourceFails(Resource resource) {
Resource relative = resource.createRelative("subdir"); assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
assertThat(relative).isEqualTo(new ClassPathResource("dir/subdir")); resource.createRelative("X").readableChannel());
} }
@Test private static Stream<Arguments> resource() throws URISyntaxException {
void fileSystemResourceWithRelativePath() throws IOException { URL resourceClass = ResourceTests.class.getResource("Resource.class");
Resource resource = new FileSystemResource("dir/"); Path resourceClassFilePath = Paths.get(resourceClass.toURI());
Resource relative = resource.createRelative("subdir"); return Stream.of(
assertThat(relative).isEqualTo(new FileSystemResource("dir/subdir")); Arguments.of(Named.of("ClassPathResource", new ClassPathResource("org/springframework/core/io/Resource.class"))),
Arguments.of(Named.of("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/Resource.class", ResourceTests.class.getClassLoader()))),
Arguments.of(Named.of("ClassPathResource with Class", new ClassPathResource("Resource.class", ResourceTests.class))),
Arguments.of(Named.of("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
Arguments.of(Named.of("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
Arguments.of(Named.of("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
Arguments.of(Named.of("UrlResource", new UrlResource(resourceClass)))
);
} }
@Test
void urlResourceWithRelativePath() throws IOException {
Resource resource = new UrlResource("file:dir/");
Resource relative = resource.createRelative("subdir");
assertThat(relative).isEqualTo(new UrlResource("file:dir/subdir"));
}
@Test @Nested
void nonFileResourceExists() throws Exception { class ByteArrayResourceTests {
URL url = new URL("https://spring.io/");
// Abort if spring.io is not reachable. @Test
assumeTrue(urlIsReachable(url)); void hasContent() throws Exception {
Resource resource = new ByteArrayResource("testString".getBytes());
Resource resource = new UrlResource(url); assertThat(resource.exists()).isTrue();
assertThat(resource.exists()).isTrue(); assertThat(resource.isOpen()).isFalse();
} String content = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
assertThat(content).isEqualTo("testString");
private boolean urlIsReachable(URL url) { assertThat(new ByteArrayResource("testString".getBytes())).isEqualTo(resource);
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD");
connection.setReadTimeout(5_000);
return connection.getResponseCode() == HttpURLConnection.HTTP_OK;
} }
catch (Exception ex) {
return false; @Test
void isNotOpen() {
Resource resource = new ByteArrayResource("testString".getBytes());
assertThat(resource.exists()).isTrue();
assertThat(resource.isOpen()).isFalse();
}
@Test
void hasDescription() {
Resource resource = new ByteArrayResource("testString".getBytes(), "my description");
assertThat(resource.getDescription().contains("my description")).isTrue();
}
}
@Nested
class InputStreamResourceTests {
@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);
}
@Test
void isOpen() {
InputStream is = new ByteArrayInputStream("testString".getBytes());
Resource resource = new InputStreamResource(is);
assertThat(resource.exists()).isTrue();
assertThat(resource.isOpen()).isTrue();
}
@Test
void hasDescription() {
InputStream is = new ByteArrayInputStream("testString".getBytes());
Resource resource = new InputStreamResource(is, "my description");
assertThat(resource.getDescription().contains("my description")).isTrue();
} }
} }
@Test
void abstractResourceExceptions() throws Exception {
final String name = "test-resource";
Resource resource = new AbstractResource() { @Nested
@Override class ClassPathResourceTests {
public String getDescription() {
return name;
}
@Override
public InputStream getInputStream() throws IOException {
throw new FileNotFoundException();
}
};
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy( @Test
resource::getURL) void equalsAndHashCode() {
.withMessageContaining(name); Resource resource = new ClassPathResource("org/springframework/core/io/Resource.class");
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy( Resource resource2 = new ClassPathResource("org/springframework/core/../core/io/./Resource.class");
resource::getFile) Resource resource3 = new ClassPathResource("org/springframework/core/").createRelative("../core/io/./Resource.class");
.withMessageContaining(name); assertThat(resource2).isEqualTo(resource);
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> assertThat(resource3).isEqualTo(resource);
resource.createRelative("/testing")) // Check whether equal/hashCode works in a HashSet.
.withMessageContaining(name); HashSet<Resource> resources = new HashSet<>();
resources.add(resource);
resources.add(resource2);
assertThat(resources.size()).isEqualTo(1);
}
@Test
void resourcesWithDifferentPathsAreEqual() {
Resource resource = new ClassPathResource("org/springframework/core/io/Resource.class", getClass().getClassLoader());
ClassPathResource sameResource = new ClassPathResource("org/springframework/core/../core/io/./Resource.class", getClass().getClassLoader());
assertThat(sameResource).isEqualTo(resource);
}
@Test
void relativeResourcesAreEqual() throws Exception {
Resource resource = new ClassPathResource("dir/");
Resource relative = resource.createRelative("subdir");
assertThat(relative).isEqualTo(new ClassPathResource("dir/subdir"));
}
assertThat(resource.getFilename()).isNull();
} }
@Test @Nested
void contentLength() throws IOException { class FileSystemResourceTests {
AbstractResource resource = new AbstractResource() {
@Override @Test
public InputStream getInputStream() { void sameResourceIsEqual() {
return new ByteArrayInputStream(new byte[] { 'a', 'b', 'c' }); String file = getClass().getResource("Resource.class").getFile();
Resource resource = new FileSystemResource(file);
assertThat(resource).isEqualTo(new FileSystemResource(file));
}
@Test
void sameResourceFromFileIsEqual() {
File file = new File(getClass().getResource("Resource.class").getFile());
Resource resource = new FileSystemResource(file);
assertThat(resource).isEqualTo(new FileSystemResource(file));
}
@Test
void sameResourceFromFilePathIsEqual() throws Exception {
Path filePath = Paths.get(getClass().getResource("Resource.class").toURI());
Resource resource = new FileSystemResource(filePath);
assertThat(resource).isEqualTo(new FileSystemResource(filePath));
}
@Test
void sameResourceFromDotPathIsEqual() {
Resource resource = new FileSystemResource("core/io/Resource.class");
assertThat(new FileSystemResource("core/../core/io/./Resource.class")).isEqualTo(resource);
}
@Test
void relativeResourcesAreEqual() throws Exception {
Resource resource = new FileSystemResource("dir/");
Resource relative = resource.createRelative("subdir");
assertThat(relative).isEqualTo(new FileSystemResource("dir/subdir"));
}
@Test
void readableChannelProvidesContent() throws Exception {
Resource resource = new FileSystemResource(getClass().getResource("Resource.class").getFile());
try (ReadableByteChannel channel = resource.readableChannel()) {
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
channel.read(buffer);
buffer.rewind();
assertThat(buffer.limit() > 0).isTrue();
} }
@Override }
public String getDescription() {
return "";
}
};
assertThat(resource.contentLength()).isEqualTo(3L);
} }
@Test @Nested
void readableChannel() throws IOException { class UrlResourceTests {
Resource resource = new FileSystemResource(getClass().getResource("Resource.class").getFile());
try (ReadableByteChannel channel = resource.readableChannel()) { private MockWebServer server = new MockWebServer();
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
channel.read(buffer); @Test
buffer.rewind(); void sameResourceWithRelativePathIsEqual() throws Exception {
assertThat(buffer.limit() > 0).isTrue(); Resource resource = new UrlResource("file:core/io/Resource.class");
assertThat(new UrlResource("file:core/../core/io/./Resource.class")).isEqualTo(resource);
}
@Test
void filenameIsExtractedFromFilePath() throws Exception {
assertThat(new UrlResource("file:/dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(new UrlResource("file:\\dir\\test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(new UrlResource("file:\\dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
}
@Test
void factoryMethodsProduceEqualResources() throws Exception {
Resource resource1 = new UrlResource("file:core/io/Resource.class");
Resource resource2 = UrlResource.from("file:core/io/Resource.class");
Resource resource3 = UrlResource.from(resource1.getURI());
assertThat(resource2.getURL()).isEqualTo(resource1.getURL());
assertThat(resource3.getURL()).isEqualTo(resource1.getURL());
assertThat(UrlResource.from("file:core/../core/io/./Resource.class")).isEqualTo(resource1);
assertThat(UrlResource.from("file:/dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(UrlResource.from("file:\\dir\\test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(UrlResource.from("file:\\dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
}
@Test
void relativeResourcesAreEqual() throws Exception {
Resource resource = new UrlResource("file:dir/");
Resource relative = resource.createRelative("subdir");
assertThat(relative).isEqualTo(new UrlResource("file:dir/subdir"));
}
@Test
void missingRemoteResourceDoesNotExist() throws Exception {
String baseUrl = startServer();
UrlResource resource = new UrlResource(baseUrl + "/missing");
assertThat(resource.exists()).isFalse();
}
@Test
void remoteResourceExists() throws Exception {
String baseUrl = startServer();
UrlResource resource = new UrlResource(baseUrl + "/resource");
assertThat(resource.exists()).isTrue();
assertThat(resource.contentLength()).isEqualTo(6);
}
@Test
void canCustomizeHttpUrlConnectionForExists() throws Exception {
String baseUrl = startServer();
CustomResource resource = new CustomResource(baseUrl + "/resource");
assertThat(resource.exists()).isTrue();
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo("HEAD");
assertThat(request.getHeader("Framework-Name")).isEqualTo("Spring");
}
@Test
void canCustomizeHttpUrlConnectionForRead() throws Exception {
String baseUrl = startServer();
CustomResource resource = new CustomResource(baseUrl + "/resource");
assertThat(resource.getInputStream()).hasContent("Spring");
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getHeader("Framework-Name")).isEqualTo("Spring");
}
@AfterEach
void shutdown() throws Exception {
this.server.shutdown();
}
private String startServer() throws Exception {
this.server.setDispatcher(new ResourceDispatcher());
this.server.start();
return "http://localhost:" + this.server.getPort();
}
class CustomResource extends UrlResource {
public CustomResource(String path) throws MalformedURLException {
super(path);
}
@Override
protected void customizeConnection(HttpURLConnection con) throws IOException {
con.setRequestProperty("Framework-Name", "Spring");
}
}
class ResourceDispatcher extends Dispatcher {
@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
if (request.getPath().equals("/resource")) {
switch (request.getMethod()) {
case "HEAD":
return new MockResponse()
.addHeader("Content-Length", "6");
case "GET":
return new MockResponse()
.addHeader("Content-Length", "6")
.addHeader("Content-Type", "text/plain")
.setBody("Spring");
}
}
return new MockResponse().setResponseCode(404);
}
} }
} }
@Test @Nested
void inputStreamNotFoundOnFileSystemResource() throws IOException { class AbstractResourceTests {
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
new FileSystemResource(getClass().getResource("Resource.class").getFile()).createRelative("X").getInputStream());
}
@Test @Test
void readableChannelNotFoundOnFileSystemResource() throws IOException { void missingResourceIsNotReadable() {
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> final String name = "test-resource";
new FileSystemResource(getClass().getResource("Resource.class").getFile()).createRelative("X").readableChannel());
}
@Test Resource resource = new AbstractResource() {
void inputStreamNotFoundOnClassPathResource() throws IOException { @Override
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> public String getDescription() {
new ClassPathResource("Resource.class", getClass()).createRelative("X").getInputStream()); return name;
} }
@Override
public InputStream getInputStream() throws IOException {
throw new FileNotFoundException();
}
};
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(resource::getURL)
.withMessageContaining(name);
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(resource::getFile)
.withMessageContaining(name);
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
resource.createRelative("/testing")).withMessageContaining(name);
assertThat(resource.getFilename()).isNull();
}
@Test
void hasContentLength() throws Exception {
AbstractResource resource = new AbstractResource() {
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(new byte[] {'a', 'b', 'c'});
}
@Override
public String getDescription() {
return "";
}
};
assertThat(resource.contentLength()).isEqualTo(3L);
}
@Test
void readableChannelNotFoundOnClassPathResource() throws IOException {
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
new ClassPathResource("Resource.class", getClass()).createRelative("X").readableChannel());
} }
} }