Automatically register directories for registered resource hints
When a hint such as `graphql/*.*` is registered for resources that are looked up via classpath scanning using a pattern such as `classpath*:graphql/**/*.graphqls`, an appropriate pattern is in fact registered in the generated `resource-config.json` file for GraalVM native images; however, classpath scanning fails since GraalVM currently does not make the `graphql` directory automatically available as a classpath resource. This can be very confusing and cumbersome for users since a file such as `graphql/schema.graphqls` will not be discovered via classpath scanning even though the file is present in the native image filesystem. To address this, this commit automatically registers resource hints for enclosing directories for a registered pattern. If the GraalVM team later decides to perform automatic directory registration, we can then remove the code introduced in conjunction with this issue. Closes gh-29403
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -46,21 +46,23 @@ class ResourceHintsTests {
|
||||
void registerType() {
|
||||
this.resourceHints.registerType(String.class);
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("java/lang/String.class"));
|
||||
patternOf("java", "java/lang", "java/lang/String.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeWithNestedType() {
|
||||
this.resourceHints.registerType(TypeReference.of(Nested.class));
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("org/springframework/aot/hint/ResourceHintsTests$Nested.class"));
|
||||
patternOf("org", "org/springframework", "org/springframework/aot", "org/springframework/aot/hint",
|
||||
"org/springframework/aot/hint/ResourceHintsTests$Nested.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeWithInnerNestedType() {
|
||||
this.resourceHints.registerType(TypeReference.of(Inner.class));
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("org/springframework/aot/hint/ResourceHintsTests$Nested$Inner.class"));
|
||||
patternOf("org", "org/springframework", "org/springframework/aot", "org/springframework/aot/hint",
|
||||
"org/springframework/aot/hint/ResourceHintsTests$Nested$Inner.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -68,16 +70,16 @@ class ResourceHintsTests {
|
||||
this.resourceHints.registerType(String.class);
|
||||
this.resourceHints.registerType(TypeReference.of(String.class));
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("java/lang/String.class"));
|
||||
patternOf("java", "java/lang", "java/lang/String.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerExactMatch() {
|
||||
void registerExactMatches() {
|
||||
this.resourceHints.registerPattern("com/example/test.properties");
|
||||
this.resourceHints.registerPattern("com/example/another.properties");
|
||||
assertThat(this.resourceHints.resourcePatternHints())
|
||||
.anySatisfy(patternOf("com/example/test.properties"))
|
||||
.anySatisfy(patternOf("com/example/another.properties"))
|
||||
.anySatisfy(patternOf("com", "com/example", "com/example/test.properties"))
|
||||
.anySatisfy(patternOf("com", "com/example", "com/example/another.properties"))
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@@ -88,11 +90,18 @@ class ResourceHintsTests {
|
||||
patternOf("/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerRootPattern() {
|
||||
this.resourceHints.registerPattern("*.properties");
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("/", "*.properties"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerPattern() {
|
||||
this.resourceHints.registerPattern("com/example/*.properties");
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("com/example/*.properties"));
|
||||
patternOf("com", "com/example", "com/example/*.properties"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -100,7 +109,7 @@ class ResourceHintsTests {
|
||||
this.resourceHints.registerPattern(resourceHint ->
|
||||
resourceHint.includes("com/example/*.properties").excludes("com/example/to-ignore.properties"));
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(patternOf(
|
||||
List.of("com/example/*.properties"),
|
||||
List.of("com", "com/example", "com/example/*.properties"),
|
||||
List.of("com/example/to-ignore.properties")));
|
||||
}
|
||||
|
||||
@@ -109,7 +118,7 @@ class ResourceHintsTests {
|
||||
this.resourceHints.registerPatternIfPresent(null, "META-INF/",
|
||||
resourceHint -> resourceHint.includes("com/example/*.properties"));
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("com/example/*.properties"));
|
||||
patternOf("com", "com/example", "com/example/*.properties"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -142,7 +151,8 @@ class ResourceHintsTests {
|
||||
String path = "org/springframework/aot/hint/support";
|
||||
ClassPathResource resource = new ClassPathResource(path);
|
||||
this.resourceHints.registerResource(resource);
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(patternOf(path));
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("org", "org/springframework", "org/springframework/aot", "org/springframework/aot/hint", path));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -150,7 +160,8 @@ class ResourceHintsTests {
|
||||
String path = "org/springframework/aot/hint/support";
|
||||
ClassPathResource resource = new ClassPathResource("support", RuntimeHints.class);
|
||||
this.resourceHints.registerResource(resource);
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(patternOf(path));
|
||||
assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(
|
||||
patternOf("org", "org/springframework", "org/springframework/aot", "org/springframework/aot/hint", path));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,7 +190,7 @@ class ResourceHintsTests {
|
||||
|
||||
private Consumer<ResourcePatternHints> patternOf(List<String> includes, List<String> excludes) {
|
||||
return pattern -> {
|
||||
assertThat(pattern.getIncludes()).map(ResourcePatternHint::getPattern).containsExactlyElementsOf(includes);
|
||||
assertThat(pattern.getIncludes()).map(ResourcePatternHint::getPattern).containsExactlyInAnyOrderElementsOf(includes);
|
||||
assertThat(pattern.getExcludes()).map(ResourcePatternHint::getPattern).containsExactlyElementsOf(excludes);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class RuntimeHintsTests {
|
||||
|
||||
private final RuntimeHints hints = new RuntimeHints();
|
||||
|
||||
|
||||
@Test
|
||||
void reflectionHintWithClass() {
|
||||
this.hints.reflection().registerType(String.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
|
||||
@@ -47,7 +48,8 @@ class RuntimeHintsTests {
|
||||
void resourceHintWithClass() {
|
||||
this.hints.resources().registerType(String.class);
|
||||
assertThat(this.hints.resources().resourcePatternHints()).singleElement().satisfies(resourceHint -> {
|
||||
assertThat(resourceHint.getIncludes()).map(ResourcePatternHint::getPattern).containsExactly("java/lang/String.class");
|
||||
assertThat(resourceHint.getIncludes()).map(ResourcePatternHint::getPattern)
|
||||
.containsExactlyInAnyOrder("java", "java/lang", "java/lang/String.class");
|
||||
assertThat(resourceHint.getExcludes()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
|
||||
private final ResourceHints hints = new ResourceHints();
|
||||
|
||||
|
||||
@Test
|
||||
void createWithInvalidName() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new FilePatternResourceHintsRegistrar(
|
||||
@@ -56,7 +57,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
new FilePatternResourceHintsRegistrar(List.of("test"), List.of(""), List.of(".txt"))
|
||||
.registerHints(this.hints, null);
|
||||
assertThat(this.hints.resourcePatternHints()).singleElement()
|
||||
.satisfies(includes("test*.txt"));
|
||||
.satisfies(includes("/", "test*.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,7 +65,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
new FilePatternResourceHintsRegistrar(List.of("test", "another"), List.of(""), List.of(".txt"))
|
||||
.registerHints(this.hints, null);
|
||||
assertThat(this.hints.resourcePatternHints()).singleElement()
|
||||
.satisfies(includes("test*.txt", "another*.txt"));
|
||||
.satisfies(includes("/" , "test*.txt", "another*.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -72,7 +73,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
new FilePatternResourceHintsRegistrar(List.of("test"), List.of("", "META-INF"), List.of(".txt"))
|
||||
.registerHints(this.hints, null);
|
||||
assertThat(this.hints.resourcePatternHints()).singleElement()
|
||||
.satisfies(includes("test*.txt", "META-INF/test*.txt"));
|
||||
.satisfies(includes("/", "test*.txt", "META-INF", "META-INF/test*.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,7 +81,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
new FilePatternResourceHintsRegistrar(List.of("test"), List.of(""), List.of(".txt", ".conf"))
|
||||
.registerHints(this.hints, null);
|
||||
assertThat(this.hints.resourcePatternHints()).singleElement()
|
||||
.satisfies(includes("test*.txt", "test*.conf"));
|
||||
.satisfies(includes("/", "test*.txt", "test*.conf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,7 +89,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
new FilePatternResourceHintsRegistrar(List.of("test"), List.of("META-INF"), List.of(".txt"))
|
||||
.registerHints(this.hints, null);
|
||||
assertThat(this.hints.resourcePatternHints()).singleElement()
|
||||
.satisfies(includes("META-INF/test*.txt"));
|
||||
.satisfies(includes("META-INF", "META-INF/test*.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -96,7 +97,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
new FilePatternResourceHintsRegistrar(List.of("test"), List.of("/"), List.of(".txt"))
|
||||
.registerHints(this.hints, null);
|
||||
assertThat(this.hints.resourcePatternHints()).singleElement()
|
||||
.satisfies(includes("test*.txt"));
|
||||
.satisfies(includes("/", "test*.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -104,7 +105,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
new FilePatternResourceHintsRegistrar(List.of("test"), List.of("classpath:META-INF"), List.of(".txt"))
|
||||
.registerHints(this.hints, null);
|
||||
assertThat(this.hints.resourcePatternHints()).singleElement()
|
||||
.satisfies(includes("META-INF/test*.txt"));
|
||||
.satisfies(includes("META-INF", "META-INF/test*.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,7 +113,7 @@ class FilePatternResourceHintsRegistrarTests {
|
||||
new FilePatternResourceHintsRegistrar(List.of("test"), List.of("classpath:/META-INF"), List.of(".txt"))
|
||||
.registerHints(this.hints, null);
|
||||
assertThat(this.hints.resourcePatternHints()).singleElement()
|
||||
.satisfies(includes("META-INF/test*.txt"));
|
||||
.satisfies(includes("META-INF", "META-INF/test*.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -49,12 +49,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Janne Valkealahti
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class FileNativeConfigurationWriterTests {
|
||||
class FileNativeConfigurationWriterTests {
|
||||
|
||||
@TempDir
|
||||
static Path tempDir;
|
||||
|
||||
|
||||
@Test
|
||||
void emptyConfig() {
|
||||
Path empty = tempDir.resolve("empty");
|
||||
@@ -174,6 +176,8 @@ public class FileNativeConfigurationWriterTests {
|
||||
"resources": {
|
||||
"includes": [
|
||||
{"pattern": "\\\\Qcom/example/test.properties\\\\E"},
|
||||
{"pattern": "\\\\Qcom\\\\E"},
|
||||
{"pattern": "\\\\Qcom/example\\\\E"},
|
||||
{"pattern": "\\\\Qcom/example/another.properties\\\\E"}
|
||||
]
|
||||
}
|
||||
@@ -191,12 +195,12 @@ public class FileNativeConfigurationWriterTests {
|
||||
resourceHints.registerPattern("com/example/test.properties");
|
||||
generator.write(hints);
|
||||
Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve(groupId).resolve(artifactId).resolve(filename);
|
||||
assertThat(jsonFile.toFile().exists()).isTrue();
|
||||
assertThat(jsonFile.toFile()).exists();
|
||||
}
|
||||
|
||||
private void assertEquals(String expectedString, String filename) throws IOException, JSONException {
|
||||
Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve(filename);
|
||||
String content = new String(Files.readAllBytes(jsonFile));
|
||||
String content = Files.readString(jsonFile);
|
||||
JSONAssert.assertEquals(expectedString, content, JSONCompareMode.NON_EXTENSIBLE);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.aot.hint.TypeReference;
|
||||
* @author Sebastien Deleuze
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ResourceHintsWriterTests {
|
||||
class ResourceHintsWriterTests {
|
||||
|
||||
@Test
|
||||
void empty() throws JSONException {
|
||||
@@ -50,6 +50,8 @@ public class ResourceHintsWriterTests {
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Qcom/example/test.properties\\\\E"},
|
||||
{ "pattern": "\\\\Qcom\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example/another.properties\\\\E"}
|
||||
]
|
||||
}
|
||||
@@ -64,7 +66,8 @@ public class ResourceHintsWriterTests {
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": ".*\\\\Q.properties\\\\E"}
|
||||
{ "pattern": ".*\\\\Q.properties\\\\E"},
|
||||
{ "pattern": "\\\\Q\\/\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
@@ -78,7 +81,9 @@ public class ResourceHintsWriterTests {
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"}
|
||||
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"},
|
||||
{ "pattern": "\\\\Qcom\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
@@ -92,7 +97,8 @@ public class ResourceHintsWriterTests {
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Qstatic/\\\\E.*"}
|
||||
{ "pattern": "\\\\Qstatic/\\\\E.*"},
|
||||
{ "pattern": "\\\\Qstatic\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
@@ -108,7 +114,11 @@ public class ResourceHintsWriterTests {
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"},
|
||||
{ "pattern": "\\\\Qorg/other/\\\\E.*\\\\Q.properties\\\\E"}
|
||||
{ "pattern": "\\\\Qcom\\\\E"},
|
||||
{ "pattern": "\\\\Qcom/example\\\\E"},
|
||||
{ "pattern": "\\\\Qorg/other/\\\\E.*\\\\Q.properties\\\\E"},
|
||||
{ "pattern": "\\\\Qorg\\\\E"},
|
||||
{ "pattern": "\\\\Qorg/other\\\\E"}
|
||||
],
|
||||
"excludes": [
|
||||
{ "pattern": "\\\\Qcom/example/to-ignore.properties\\\\E"},
|
||||
@@ -126,7 +136,9 @@ public class ResourceHintsWriterTests {
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example/test.properties\\\\E"}
|
||||
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example/test.properties\\\\E"},
|
||||
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom\\\\E"},
|
||||
{ "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example\\\\E"}
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
@@ -140,7 +152,9 @@ public class ResourceHintsWriterTests {
|
||||
{
|
||||
"resources": {
|
||||
"includes": [
|
||||
{ "pattern": "\\\\Qjava/lang/String.class\\\\E"}
|
||||
{ "pattern": "\\\\Qjava/lang/String.class\\\\E" },
|
||||
{ "pattern": "\\\\Qjava\\\\E" },
|
||||
{ "pattern": "\\\\Qjava/lang\\\\E" }
|
||||
]
|
||||
}
|
||||
}""", hints);
|
||||
|
||||
Reference in New Issue
Block a user