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 365b50e216a..2f146988379 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. @@ -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; @@ -642,13 +643,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()) { @@ -801,7 +804,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); @@ -828,11 +831,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. @@ -869,7 +878,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()); @@ -1099,6 +1114,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. 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 ba027c5705c..2856f549855 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. @@ -278,24 +283,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(); + } } /** 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 580eba7c388..4ca14f99a3f 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. @@ -181,6 +181,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); }