Browse Source

Support default package for TypeReference in ResourceHintsPredicates

Prior to this commit, if the TypeReference supplied to
ResourceHintsPredicates.forResource(TypeReference,String) was for a
class declared in the default package (i.e., without a package), the
resolveAbsoluteResourceName() method incorrectly prepended two leading
slashes (//) to the absolute resource name, causing correct matches to
fail.

This commit fixes this by adding special handling for a TypeReference
without a package name. In addition, this commit introduces lenient
handling of resource names by consistently removing a leading slash in
ResourceHintsPredicates.forResource(*) methods. The latter aligns with
absolute resource path handling in other places in the framework, such
as ClassPathResource.

Closes gh-29086
pull/29130/head
Sam Brannen 3 years ago
parent
commit
97b98c3378
  1. 9
      spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java
  2. 24
      spring-core/src/main/java/org/springframework/aot/hint/predicate/ResourceHintsPredicates.java
  3. 39
      spring-core/src/test/java/org/springframework/aot/hint/predicate/ResourceHintsPredicatesTests.java

9
spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java

@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link InstrumentedMethod}. * Tests for {@link InstrumentedMethod}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Sam Brannen
*/ */
class InstrumentedMethodTests { class InstrumentedMethodTests {
@ -556,7 +557,7 @@ class InstrumentedMethodTests {
void classGetResourceShouldMatchResourcePatternWhenAbsolute() { void classGetResourceShouldMatchResourcePatternWhenAbsolute() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE) RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build(); .onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
hints.resources().registerPattern("/some/*"); hints.resources().registerPattern("some/*");
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
} }
@ -564,7 +565,7 @@ class InstrumentedMethodTests {
void classGetResourceShouldMatchResourcePatternWhenRelative() { void classGetResourceShouldMatchResourcePatternWhenRelative() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE) RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(InstrumentedMethodTests.class).withArgument("resource.txt").build(); .onInstance(InstrumentedMethodTests.class).withArgument("resource.txt").build();
hints.resources().registerPattern("/org/springframework/aot/agent/*"); hints.resources().registerPattern("org/springframework/aot/agent/*");
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
} }
@ -572,7 +573,7 @@ class InstrumentedMethodTests {
void classGetResourceShouldNotMatchResourcePatternWhenInvalid() { void classGetResourceShouldNotMatchResourcePatternWhenInvalid() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE) RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build(); .onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
hints.resources().registerPattern("/other/*"); hints.resources().registerPattern("other/*");
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation); assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
} }
@ -580,7 +581,7 @@ class InstrumentedMethodTests {
void classGetResourceShouldNotMatchResourcePatternWhenExcluded() { void classGetResourceShouldNotMatchResourcePatternWhenExcluded() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE) RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build(); .onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
hints.resources().registerPattern(resourceHint -> resourceHint.includes("/some/*").excludes("/some/path/*")); hints.resources().registerPattern(resourceHint -> resourceHint.includes("some/*").excludes("some/path/*"));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation); assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
} }

24
spring-core/src/main/java/org/springframework/aot/hint/predicate/ResourceHintsPredicates.java

@ -34,6 +34,7 @@ import org.springframework.util.ConcurrentLruCache;
* *
* @author Brian Clozel * @author Brian Clozel
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
* @since 6.0 * @since 6.0
*/ */
public class ResourceHintsPredicates { public class ResourceHintsPredicates {
@ -58,7 +59,11 @@ public class ResourceHintsPredicates {
* Return a predicate that checks whether a resource hint is registered for the given * Return a predicate that checks whether a resource hint is registered for the given
* resource name, located in the given type's package. * resource name, located in the given type's package.
* <p>For example, {@code forResource(org.example.MyClass, "myResource.txt")} * <p>For example, {@code forResource(org.example.MyClass, "myResource.txt")}
* will match for {@code "/org/example/myResource.txt"}. * will match against {@code "org/example/myResource.txt"}.
* <p>If the given resource name is an absolute path (i.e., starts with a
* leading slash), the supplied type will be ignored. For example,
* {@code forResource(org.example.MyClass, "/myResource.txt")} will match against
* {@code "myResource.txt"}.
* @param type the type's package where to look for the resource * @param type the type's package where to look for the resource
* @param resourceName the resource name * @param resourceName the resource name
* @return the {@link RuntimeHints} predicate * @return the {@link RuntimeHints} predicate
@ -69,32 +74,39 @@ public class ResourceHintsPredicates {
} }
private String resolveAbsoluteResourceName(TypeReference type, String resourceName) { private String resolveAbsoluteResourceName(TypeReference type, String resourceName) {
// absolute path
if (resourceName.startsWith("/")) { if (resourceName.startsWith("/")) {
return resourceName.substring(1);
}
// default package
else if (type.getPackageName().isEmpty()) {
return resourceName; return resourceName;
} }
// relative path
else { else {
return "/" + type.getPackageName().replace('.', '/') return type.getPackageName().replace('.', '/') + "/" + resourceName;
+ "/" + resourceName;
} }
} }
/** /**
* Return a predicate that checks whether a resource hint is registered for * Return a predicate that checks whether a resource hint is registered for
* the given resource name. * the given resource name.
* @param resourceName the full resource name * <p>A leading slash will be removed.
* @param resourceName the absolute resource name
* @return the {@link RuntimeHints} predicate * @return the {@link RuntimeHints} predicate
*/ */
public Predicate<RuntimeHints> forResource(String resourceName) { public Predicate<RuntimeHints> forResource(String resourceName) {
String resourceNameToUse = (resourceName.startsWith("/") ? resourceName.substring(1) : resourceName);
return hints -> { return hints -> {
AggregatedResourcePatternHints aggregatedResourcePatternHints = AggregatedResourcePatternHints.of( AggregatedResourcePatternHints aggregatedResourcePatternHints = AggregatedResourcePatternHints.of(
hints.resources()); hints.resources());
boolean isExcluded = aggregatedResourcePatternHints.excludes().stream().anyMatch(excluded -> boolean isExcluded = aggregatedResourcePatternHints.excludes().stream().anyMatch(excluded ->
CACHED_RESOURCE_PATTERNS.get(excluded).matcher(resourceName).matches()); CACHED_RESOURCE_PATTERNS.get(excluded).matcher(resourceNameToUse).matches());
if (isExcluded) { if (isExcluded) {
return false; return false;
} }
return aggregatedResourcePatternHints.includes().stream().anyMatch(included -> return aggregatedResourcePatternHints.includes().stream().anyMatch(included ->
CACHED_RESOURCE_PATTERNS.get(included).matcher(resourceName).matches()); CACHED_RESOURCE_PATTERNS.get(included).matcher(resourceNameToUse).matches());
}; };
} }

39
spring-core/src/test/java/org/springframework/aot/hint/predicate/ResourceHintsPredicatesTests.java

@ -18,7 +18,6 @@ package org.springframework.aot.hint.predicate;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
@ -30,40 +29,54 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link ReflectionHintsPredicates}. * Tests for {@link ReflectionHintsPredicates}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Sam Brannen
*/ */
class ResourceHintsPredicatesTests { class ResourceHintsPredicatesTests {
private final ResourceHintsPredicates resources = new ResourceHintsPredicates(); private final ResourceHintsPredicates resources = new ResourceHintsPredicates();
private RuntimeHints runtimeHints; private final RuntimeHints runtimeHints = new RuntimeHints();
@BeforeEach
void setup() {
this.runtimeHints = new RuntimeHints();
}
@Test @Test
void resourcePatternMatchesResourceName() { void resourcePatternMatchesResourceName() {
this.runtimeHints.resources().registerPattern("/test/*"); this.runtimeHints.resources().registerPattern("test/*");
assertPredicateMatches(resources.forResource("/test/spring.properties")); assertPredicateMatches(resources.forResource("/test/spring.properties"));
} }
@Test @Test
void resourcePatternDoesNotMatchResourceName() { void resourcePatternDoesNotMatchResourceName() {
this.runtimeHints.resources().registerPattern("/test/spring.*"); this.runtimeHints.resources().registerPattern("test/spring.*");
assertPredicateDoesNotMatch(resources.forResource("/test/other.properties")); assertPredicateDoesNotMatch(resources.forResource("/test/other.properties"));
} }
@Test @Test
void resourcePatternMatchesTypeAndResourceName() { void resourcePatternMatchesTypeAndResourceName() {
this.runtimeHints.resources().registerPattern("/org/springframework/aot/hint/predicate/spring.*"); this.runtimeHints.resources().registerPattern("org/springframework/aot/hint/predicate/spring.*");
assertPredicateMatches(resources.forResource(TypeReference.of(getClass()), "spring.properties")); assertPredicateMatches(resources.forResource(TypeReference.of(getClass()), "spring.properties"));
} }
@Test
void resourcePatternMatchesTypeAndAbsoluteResourceName() {
this.runtimeHints.resources().registerPattern("spring.*");
assertPredicateMatches(resources.forResource(TypeReference.of(getClass()), "/spring.properties"));
}
@Test
void resourcePatternMatchesTypeInDefaultPackageAndResourceName() {
this.runtimeHints.resources().registerPattern("spring.*");
assertPredicateMatches(resources.forResource(TypeReference.of("DummyClass"), "spring.properties"));
}
@Test
void resourcePatternMatchesTypeInDefaultPackageAndAbsoluteResourceName() {
this.runtimeHints.resources().registerPattern("spring.*");
assertPredicateMatches(resources.forResource(TypeReference.of("DummyClass"), "/spring.properties"));
}
@Test @Test
void resourcePatternDoesNotMatchTypeAndResourceName() { void resourcePatternDoesNotMatchTypeAndResourceName() {
this.runtimeHints.resources().registerPattern("/spring.*"); this.runtimeHints.resources().registerPattern("spring.*");
assertPredicateDoesNotMatch(resources.forResource(TypeReference.of(getClass()), "spring.properties")); assertPredicateDoesNotMatch(resources.forResource(TypeReference.of(getClass()), "spring.properties"));
} }
@ -81,11 +94,11 @@ class ResourceHintsPredicatesTests {
private void assertPredicateMatches(Predicate<RuntimeHints> predicate) { private void assertPredicateMatches(Predicate<RuntimeHints> predicate) {
assertThat(predicate.test(this.runtimeHints)).isTrue(); assertThat(predicate).accepts(this.runtimeHints);
} }
private void assertPredicateDoesNotMatch(Predicate<RuntimeHints> predicate) { private void assertPredicateDoesNotMatch(Predicate<RuntimeHints> predicate) {
assertThat(predicate.test(this.runtimeHints)).isFalse(); assertThat(predicate).rejects(this.runtimeHints);
} }
} }

Loading…
Cancel
Save