From c48fec885c631285519b33571723f831cd3e7654 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 12 Jan 2025 18:00:10 +0100 Subject: [PATCH 1/5] Avoid caching invalid root directories Closes gh-34111 --- .../PathMatchingResourcePatternResolver.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 3862094d2a1..657f739bc43 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.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. @@ -645,13 +645,15 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol } } if (currentPrefix != null) { - // A prefix match found, potentially to be turned into a common parent cache entry. - if (commonPrefix == null || !commonUnique || currentPrefix.length() > commonPrefix.length()) { - commonPrefix = currentPrefix; - existingPath = path; - } - else if (currentPrefix.equals(commonPrefix)) { - commonUnique = false; + if (checkPathWithinPackage(path.substring(currentPrefix.length()))) { + // A prefix match found, potentially to be turned into a common parent cache entry. + if (commonPrefix == null || !commonUnique || currentPrefix.length() > commonPrefix.length()) { + commonPrefix = currentPrefix; + existingPath = path; + } + else if (currentPrefix.equals(commonPrefix)) { + commonUnique = false; + } } } else if (actualRootPath == null || path.length() > actualRootPath.length()) { @@ -1103,6 +1105,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol return (path.startsWith("/") ? path.substring(1) : path); } + private static boolean checkPathWithinPackage(String path) { + return (path.contains("/") && !path.contains(ResourceUtils.JAR_URL_SEPARATOR)); + } + /** * Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime. From 0f26f42da7dc77d0f5b6b640e4268ae3fe9b16ef Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 12 Jan 2025 18:05:49 +0100 Subject: [PATCH 2/5] Defensively check for jar separator in jar entry names Closes gh-34126 --- .../support/PathMatchingResourcePatternResolver.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 657f739bc43..f8d52657a0c 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -40,6 +40,7 @@ import java.nio.file.Path; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.NavigableSet; @@ -806,7 +807,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (separatorIndex == -1) { separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR); } - if (separatorIndex != -1) { + if (separatorIndex >= 0) { jarFileUrl = urlFile.substring(0, separatorIndex); rootEntryPath = urlFile.substring(separatorIndex + 2); // both separators are 2 chars NavigableSet entriesCache = this.jarEntriesCache.get(jarFileUrl); @@ -874,7 +875,13 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol } Set result = new LinkedHashSet<>(64); NavigableSet entriesCache = new TreeSet<>(); - for (String entryPath : jarFile.stream().map(JarEntry::getName).sorted().toList()) { + Iterator entryIterator = jarFile.stream().map(JarEntry::getName).sorted().iterator(); + while (entryIterator.hasNext()) { + String entryPath = entryIterator.next(); + int entrySeparatorIndex = entryPath.indexOf(ResourceUtils.JAR_URL_SEPARATOR); + if (entrySeparatorIndex >= 0) { + entryPath = entryPath.substring(entrySeparatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length()); + } entriesCache.add(entryPath); if (entryPath.startsWith(rootEntryPath)) { String relativePath = entryPath.substring(rootEntryPath.length()); From 36fd82f32f03cfcded746522a501bcc1c8b24fa2 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 12 Jan 2025 18:07:09 +0100 Subject: [PATCH 3/5] Defensively resolve JarFile from JarURLConnection Closes gh-34216 --- .../PathMatchingResourcePatternResolver.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index f8d52657a0c..a6899040fdf 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -834,11 +834,17 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (con instanceof JarURLConnection jarCon) { // Should usually be the case for traditional JAR files. - jarFile = jarCon.getJarFile(); - jarFileUrl = jarCon.getJarFileURL().toExternalForm(); - JarEntry jarEntry = jarCon.getJarEntry(); - rootEntryPath = (jarEntry != null ? jarEntry.getName() : ""); - closeJarFile = !jarCon.getUseCaches(); + try { + jarFile = jarCon.getJarFile(); + jarFileUrl = jarCon.getJarFileURL().toExternalForm(); + JarEntry jarEntry = jarCon.getJarEntry(); + rootEntryPath = (jarEntry != null ? jarEntry.getName() : ""); + closeJarFile = !jarCon.getUseCaches(); + } + catch (FileNotFoundException ex) { + // Happens in case of cached root directory without specific subdirectory present. + return Collections.emptySet(); + } } else { // No JarURLConnection -> need to resort to URL file parsing. From 2bb4df79c31c07e58506de8a95eb0330c6bd0113 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 12 Jan 2025 18:08:54 +0100 Subject: [PATCH 4/5] Ignore SQLServerException with "not supported" message Closes gh-34233 --- .../jdbc/datasource/JdbcTransactionObjectSupport.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java index 49421e52fcf..aaa09e3a691 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.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. @@ -183,6 +183,13 @@ public abstract class JdbcTransactionObjectSupport implements SavepointManager, catch (SQLFeatureNotSupportedException ex) { // typically on Oracle - ignore } + catch (SQLException ex) { + // ignore Microsoft SQLServerException: This operation is not supported. + String msg = ex.getMessage(); + if (msg == null || !msg.contains("not supported")) { + throw new TransactionSystemException("Could not explicitly release JDBC savepoint", ex); + } + } catch (Throwable ex) { throw new TransactionSystemException("Could not explicitly release JDBC savepoint", ex); } From 25287205baf85a6c381e2035afadd29d74862d1b Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 12 Jan 2025 18:11:47 +0100 Subject: [PATCH 5/5] Use ReentrantLock for compilation without synchronization Closes gh-34133 --- .../jdbc/core/simple/AbstractJdbcCall.java | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java index d09c7e9e6c7..71d8589a1ee 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -21,6 +21,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; @@ -66,6 +68,9 @@ public abstract class AbstractJdbcCall { /** List of RefCursor/ResultSet RowMapper objects. */ private final Map> declaredRowMappers = new LinkedHashMap<>(); + /** Lock for the compilation step. */ + private final Lock compilationLock = new ReentrantLock(); + /** * Has this operation been compiled? Compilation means at least checking * that a DataSource or JdbcTemplate has been provided. @@ -284,24 +289,30 @@ public abstract class AbstractJdbcCall { * @throws org.springframework.dao.InvalidDataAccessApiUsageException if the object hasn't * been correctly initialized, for example if no DataSource has been provided */ - public final synchronized void compile() throws InvalidDataAccessApiUsageException { - if (!isCompiled()) { - if (getProcedureName() == null) { - throw new InvalidDataAccessApiUsageException("Procedure or Function name is required"); - } - try { - this.jdbcTemplate.afterPropertiesSet(); - } - catch (IllegalArgumentException ex) { - throw new InvalidDataAccessApiUsageException(ex.getMessage()); - } - compileInternal(); - this.compiled = true; - if (logger.isDebugEnabled()) { - logger.debug("SqlCall for " + (isFunction() ? "function" : "procedure") + - " [" + getProcedureName() + "] compiled"); + public final void compile() throws InvalidDataAccessApiUsageException { + this.compilationLock.lock(); + try { + if (!isCompiled()) { + if (getProcedureName() == null) { + throw new InvalidDataAccessApiUsageException("Procedure or Function name is required"); + } + try { + this.jdbcTemplate.afterPropertiesSet(); + } + catch (IllegalArgumentException ex) { + throw new InvalidDataAccessApiUsageException(ex.getMessage()); + } + compileInternal(); + this.compiled = true; + if (logger.isDebugEnabled()) { + logger.debug("SqlCall for " + (isFunction() ? "function" : "procedure") + + " [" + getProcedureName() + "] compiled"); + } } } + finally { + this.compilationLock.unlock(); + } } /**