From 760376c3186baa1164ab84d24e2f798d5f56812c Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 17 Mar 2025 19:20:41 +0100 Subject: [PATCH] Restore check for jar root existence (now via getEntryName/getJarEntry) Closes gh-34607 --- .../io/AbstractFileResolvingResource.java | 17 +++++++----- .../springframework/core/io/UrlResource.java | 6 ++--- ...hMatchingResourcePatternResolverTests.java | 27 +++++++++++-------- 3 files changed, 30 insertions(+), 20 deletions(-) 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 84eff6587b..2dcfb4f322 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 @@ -56,6 +56,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { // Try a URL connection content-length header URLConnection con = url.openConnection(); customizeConnection(con); + HttpURLConnection httpCon = (con instanceof HttpURLConnection huc ? huc : null); if (httpCon != null) { httpCon.setRequestMethod("HEAD"); @@ -81,12 +82,16 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { } } } - // Check content-length entry but not for JarURLConnection where - // this would open the jar file but effectively never close it -> - // for jar entries, always fall back to stream existence instead. - if (!(con instanceof JarURLConnection) && con.getContentLengthLong() > 0) { + + if (con instanceof JarURLConnection jarCon) { + // For JarURLConnection, do not check content-length but rather the + // existence of the entry (or the jar root in case of no entryName). + return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null); + } + else if (con.getContentLengthLong() > 0) { return true; } + if (httpCon != null) { // No HTTP OK status, and no content-length header: give up httpCon.disconnect(); @@ -346,8 +351,8 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { */ protected void customizeConnection(URLConnection con) throws IOException { ResourceUtils.useCachesIfNecessary(con); - if (con instanceof HttpURLConnection httpConn) { - customizeConnection(httpConn); + if (con instanceof HttpURLConnection httpCon) { + customizeConnection(httpCon); } } diff --git a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java index 4c5c3e0226..0f10059787 100644 --- a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -234,8 +234,8 @@ public class UrlResource extends AbstractFileResolvingResource { } catch (IOException ex) { // Close the HTTP connection (if applicable). - if (con instanceof HttpURLConnection httpConn) { - httpConn.disconnect(); + if (con instanceof HttpURLConnection httpCon) { + httpCon.disconnect(); } throw ex; } diff --git a/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java b/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java index ee8b7352da..af1d12e0b4 100644 --- a/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java @@ -51,8 +51,10 @@ import org.junit.jupiter.api.io.TempDir; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; import org.springframework.util.ClassUtils; import org.springframework.util.FileSystemUtils; +import org.springframework.util.ResourceUtils; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; @@ -133,6 +135,7 @@ class PathMatchingResourcePatternResolverTests { assertExactFilenames("classpath*:scanned/*.txt", "resource#test1.txt", "resource#test2.txt"); } + @Nested class WithHashtagsInTheirFilenames { @@ -299,6 +302,7 @@ class PathMatchingResourcePatternResolverTests { } } + @Nested class ClassPathManifestEntries { @@ -313,8 +317,8 @@ class PathMatchingResourcePatternResolverTests { writeApplicationJar(this.temp.resolve("app.jar")); String java = ProcessHandle.current().info().command().get(); Process process = new ProcessBuilder(java, "-jar", "app.jar") - .directory(this.temp.toFile()) - .start(); + .directory(this.temp.toFile()) + .start(); assertThat(process.waitFor()).isZero(); String result = StreamUtils.copyToString(process.getInputStream(), StandardCharsets.UTF_8); assertThat(result.replace("\\", "/")).contains("!!!!").contains("/lib/asset.jar!/assets/file.txt"); @@ -328,6 +332,8 @@ class PathMatchingResourcePatternResolverTests { StreamUtils.copy("test", StandardCharsets.UTF_8, jar); jar.closeEntry(); } + assertThat(new FileSystemResource(path).exists()).isTrue(); + assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue(); } private void writeApplicationJar(Path path) throws Exception { @@ -338,8 +344,7 @@ class PathMatchingResourcePatternResolverTests { mainAttributes.put(Name.MANIFEST_VERSION, "1.0"); try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()), manifest)) { String appClassResource = ClassUtils.convertClassNameToResourcePath( - ClassPathManifestEntriesTestApplication.class.getName()) - + ClassUtils.CLASS_FILE_SUFFIX; + ClassPathManifestEntriesTestApplication.class.getName()) + ClassUtils.CLASS_FILE_SUFFIX; String folder = ""; for (String name : appClassResource.split("/")) { if (!name.endsWith(ClassUtils.CLASS_FILE_SUFFIX)) { @@ -356,18 +361,19 @@ class PathMatchingResourcePatternResolverTests { } } } + assertThat(new FileSystemResource(path).exists()).isTrue(); + assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue(); } private String buildSpringClassPath() throws Exception { - return copyClasses(PathMatchingResourcePatternResolver.class, "spring-core") - + copyClasses(LogFactory.class, "commons-logging"); + return copyClasses(PathMatchingResourcePatternResolver.class, "spring-core") + + copyClasses(LogFactory.class, "commons-logging"); } - private String copyClasses(Class sourceClass, String destinationName) - throws URISyntaxException, IOException { + private String copyClasses(Class sourceClass, String destinationName) throws URISyntaxException, IOException { Path destination = this.temp.resolve(destinationName); - String resourcePath = ClassUtils.convertClassNameToResourcePath(sourceClass.getName()) - + ClassUtils.CLASS_FILE_SUFFIX; + String resourcePath = ClassUtils.convertClassNameToResourcePath( + sourceClass.getName()) + ClassUtils.CLASS_FILE_SUFFIX; URL resource = getClass().getClassLoader().getResource(resourcePath); URL url = new URL(resource.toString().replace(resourcePath, "")); URLConnection connection = url.openConnection(); @@ -393,7 +399,6 @@ class PathMatchingResourcePatternResolverTests { } return destinationName + "/ "; } - }