From 989eb37fb7e03bea0bcb66da9bd7846dd3c12a21 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 29 Nov 2024 14:43:52 +0100 Subject: [PATCH] Update AOT resource hints with new GraalVM behavior Prior to this commit, the resource hints for AOT applications would generate JSON metadata with `java.util.regex.Pattern`, like: ``` { "resources": { "includes": [ { "pattern": "\\Qbanner.txt\\E" } ] } } ``` This regexp feature, as well as "includes" and "excludes" are not supported anymore with the new metadata format. This commit removes the pattern format which is now replaced by a "glob" format. "globs" should only contain "*" (zero or more characters in a path segment) or "**" (zero or more path segments). Some instances of resource hint registration should be migrated as a result. For example, "/files/*.ext" matched both "/files/a.ext" and "/files/folder/b.txt" in the past. The new behavior matches only the former, unless the "/files/**/*.ext" glob pattern is used. This commit also removes the "excludes" support, which was not widely used and very often could lead to subtle behavior. Closes gh-31340 --- .../aot/hint/ResourcePatternHint.java | 35 +++++++------- .../aot/hint/ResourcePatternHints.java | 39 +--------------- .../aot/hint/ResourcePatternHintTests.java | 46 +++++++++++-------- 3 files changed, 44 insertions(+), 76 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java index e686a86a3a..b403e7d4df 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java @@ -16,12 +16,10 @@ package org.springframework.aot.hint; -import java.util.Arrays; import java.util.Objects; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import org.springframework.lang.Nullable; +import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; /** @@ -31,16 +29,18 @@ import org.springframework.util.Assert; * resource on the classpath, or alternatively may contain the special * {@code *} character to indicate a wildcard match. For example: * * *

A resource pattern must not start with a slash ({@code /}) unless it is the @@ -54,6 +54,8 @@ import org.springframework.util.Assert; */ public final class ResourcePatternHint implements ConditionalHint { + private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); + private final String pattern; @Nullable @@ -77,16 +79,11 @@ public final class ResourcePatternHint implements ConditionalHint { } /** - * Return the regex {@link Pattern} to use for identifying the resources to match. + * Whether the given path matches the current glob pattern. + * @param path the path to match against */ - public Pattern toRegex() { - String prefix = (this.pattern.startsWith("*") ? ".*" : ""); - String suffix = (this.pattern.endsWith("*") ? ".*" : ""); - String regex = Arrays.stream(this.pattern.split("\\*")) - .filter(s -> !s.isEmpty()) - .map(Pattern::quote) - .collect(Collectors.joining(".*", prefix, suffix)); - return Pattern.compile(regex); + public boolean matches(String path) { + return PATH_MATCHER.match(this.pattern, path); } @Nullable diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java index 78c8ce9a6e..952c441392 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java @@ -38,12 +38,9 @@ public final class ResourcePatternHints { private final List includes; - private final List excludes; - private ResourcePatternHints(Builder builder) { this.includes = new ArrayList<>(builder.includes); - this.excludes = new ArrayList<>(builder.excludes); } /** @@ -54,14 +51,6 @@ public final class ResourcePatternHints { return this.includes; } - /** - * Return the exclude patterns to use to identify the resources to match. - * @return the exclude patterns - */ - public List getExcludes() { - return this.excludes; - } - /** * Builder for {@link ResourcePatternHints}. @@ -70,13 +59,11 @@ public final class ResourcePatternHints { private final Set includes = new LinkedHashSet<>(); - private final Set excludes = new LinkedHashSet<>(); - Builder() { } /** - * Include resources matching the specified patterns. + * Include resources matching the specified glob patterns. * @param reachableType the type that should be reachable for this hint to apply * @param includes the include patterns (see {@link ResourcePatternHint} documentation) * @return {@code this}, to facilitate method chaining @@ -129,7 +116,7 @@ public final class ResourcePatternHints { } /** - * Include resources matching the specified patterns. + * Include resources matching the specified glob patterns. * @param includes the include patterns (see {@link ResourcePatternHint} documentation) * @return {@code this}, to facilitate method chaining */ @@ -137,28 +124,6 @@ public final class ResourcePatternHints { return includes(null, includes); } - /** - * Exclude resources matching the specified patterns. - * @param reachableType the type that should be reachable for this hint to apply - * @param excludes the exclude patterns (see {@link ResourcePatternHint} documentation) - * @return {@code this}, to facilitate method chaining - */ - public Builder excludes(@Nullable TypeReference reachableType, String... excludes) { - List newExcludes = Arrays.stream(excludes) - .map(include -> new ResourcePatternHint(include, reachableType)).toList(); - this.excludes.addAll(newExcludes); - return this; - } - - /** - * Exclude resources matching the specified patterns. - * @param excludes the exclude patterns (see {@link ResourcePatternHint} documentation) - * @return {@code this}, to facilitate method chaining - */ - public Builder excludes(String... excludes) { - return excludes(null, excludes); - } - /** * Create {@link ResourcePatternHints} based on the state of this * builder. diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ResourcePatternHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ResourcePatternHintTests.java index 303b243b2b..ecea7ee915 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ResourcePatternHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ResourcePatternHintTests.java @@ -39,49 +39,55 @@ class ResourcePatternHintTests { @Test void rootDirectory() { ResourcePatternHint hint = new ResourcePatternHint("/", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("/") - .rejects("/com/example", "/file.txt"); + assertThat(hint.matches("/")).isTrue(); + assertThat(hint.matches("/com/example")).isFalse(); + assertThat(hint.matches("/file.txt")).isFalse(); } @Test void fileAtRoot() { ResourcePatternHint hint = new ResourcePatternHint("file.properties", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("file.properties") - .rejects("com/example/file.properties", "file.prop", "another-file.properties"); + assertThat(hint.matches("file.properties")).isTrue(); + assertThat(hint.matches("com/example/file.properties")).isFalse(); + assertThat(hint.matches("file.prop")).isFalse(); + assertThat(hint.matches("another-file.properties")).isFalse(); } @Test void fileInDirectory() { ResourcePatternHint hint = new ResourcePatternHint("com/example/file.properties", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("com/example/file.properties") - .rejects("file.properties", "com/file.properties", "com/example/another-file.properties"); + assertThat(hint.matches("com/example/file.properties")).isTrue(); + assertThat(hint.matches("file.properties")).isFalse(); + assertThat(hint.matches("com/file.properties")).isFalse(); + assertThat(hint.matches("com/example/another-file.properties")).isFalse(); } @Test void extension() { - ResourcePatternHint hint = new ResourcePatternHint("*.properties", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("file.properties", "com/example/file.properties") - .rejects("file.prop", "com/example/file.prop"); + ResourcePatternHint hint = new ResourcePatternHint("**/*.properties", null); + assertThat(hint.matches("file.properties")).isTrue(); + assertThat(hint.matches("com/example/file.properties")).isTrue(); + assertThat(hint.matches("file.prop")).isFalse(); + assertThat(hint.matches("com/example/file.prop")).isFalse(); } @Test void extensionInDirectoryAtAnyDepth() { ResourcePatternHint hint = new ResourcePatternHint("com/example/*.properties", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("com/example/file.properties", "com/example/another/file.properties") - .rejects("file.properties", "com/file.properties"); + assertThat(hint.matches("com/example/file.properties")).isTrue(); + assertThat(hint.matches("com/example/another/file.properties")).isFalse(); + assertThat(hint.matches("com/file.properties")).isFalse(); + assertThat(hint.matches("file.properties")).isFalse(); } @Test void anyFileInDirectoryAtAnyDepth() { - ResourcePatternHint hint = new ResourcePatternHint("com/example/*", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("com/example/file.properties", "com/example/another/file.properties", "com/example/another") - .rejects("file.properties", "com/file.properties"); + ResourcePatternHint hint = new ResourcePatternHint("com/example/**", null); + assertThat(hint.matches("com/example/file.properties")).isTrue(); + assertThat(hint.matches("com/example/another/file.properties")).isTrue(); + assertThat(hint.matches("com/example/another")).isTrue(); + assertThat(hint.matches("file.properties")).isFalse(); + assertThat(hint.matches("com/file.properties")).isFalse(); } }