Skip to content

Commit

Permalink
Introduce RuntimeHintsUtils.registerResourceIfNecessary
Browse files Browse the repository at this point in the history
This commit introduces a new registerResourceIfNecessary() method in
RuntimeHintsUtils that simplifies the registration of hints for
`classpath:` resources.

Closes gh-29083
  • Loading branch information
sbrannen committed Sep 6, 2022
1 parent dd1e6b9 commit 28c492c
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
import java.util.function.Consumer;

import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ResourceHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

/**
* Utility methods for runtime hints support code.
Expand Down Expand Up @@ -92,4 +95,18 @@ public static void registerAnnotationIfNecessary(RuntimeHints hints, MergedAnnot
}
}

/**
* Determine if the supplied resource is a {@link ClassPathResource} that
* {@linkplain Resource#exists() exists} and register the resource for run-time
* availability accordingly.
* @param hints the {@link RuntimeHints} instance to use
* @param resource the resource to register
* @see ResourceHints#registerPattern(String)
*/
public static void registerResourceIfNecessary(RuntimeHints hints, Resource resource) {
if (resource instanceof ClassPathResource classPathResource && classPathResource.exists()) {
hints.resources().registerPattern(classPathResource.getPath());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import org.springframework.aot.hint.JdkProxyHint;
Expand All @@ -28,8 +29,11 @@
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DescriptiveResource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.resource;

/**
* Tests for {@link RuntimeHintsUtils}.
Expand All @@ -41,6 +45,39 @@ class RuntimeHintsUtilsTests {

private final RuntimeHints hints = new RuntimeHints();

@Test
void registerResourceIfNecessaryWithUnsupportedResourceType() {
DescriptiveResource resource = new DescriptiveResource("bogus");
RuntimeHintsUtils.registerResourceIfNecessary(this.hints, resource);
assertThat(this.hints.resources().resourcePatterns()).isEmpty();
}

@Test
void registerResourceIfNecessaryWithNonexistentClassPathResource() {
ClassPathResource resource = new ClassPathResource("bogus", getClass());
RuntimeHintsUtils.registerResourceIfNecessary(this.hints, resource);
assertThat(this.hints.resources().resourcePatterns()).isEmpty();
}

@Test
void registerResourceIfNecessaryWithExistingClassPathResource() {
String path = "org/springframework/aot/hint/support";
ClassPathResource resource = new ClassPathResource(path);
RuntimeHintsUtils.registerResourceIfNecessary(this.hints, resource);
assertThat(resource().forResource(path)).accepts(this.hints);
}

@Disabled("Disabled since ClassPathResource.getPath() does not honor its contract for relative resources")
@Test
void registerResourceIfNecessaryWithExistingRelativeClassPathResource() {
String path = "org/springframework/aot/hint/support";
ClassPathResource resource = new ClassPathResource("support", RuntimeHints.class);
RuntimeHintsUtils.registerResourceIfNecessary(this.hints, resource);
// This unfortunately fails since ClassPathResource.getPath() returns
// "support" instead of "org/springframework/aot/hint/support".
assertThat(resource().forResource(path)).accepts(this.hints);
}

@Test
@SuppressWarnings("deprecation")
void registerSynthesizedAnnotation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import java.util.List;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.support.RuntimeHintsUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ActiveProfilesResolver;
import org.springframework.test.context.ContextLoader;
Expand Down Expand Up @@ -50,12 +52,12 @@ class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar {
public void registerHints(MergedContextConfiguration mergedConfig, List<Class<?>> testClasses,
RuntimeHints runtimeHints, ClassLoader classLoader) {

registerHintsForMergedContextConfiguration(runtimeHints, mergedConfig);
registerHintsForMergedContextConfiguration(runtimeHints, classLoader, mergedConfig);
testClasses.forEach(testClass -> registerHintsForActiveProfilesResolvers(runtimeHints, testClass));
}

private void registerHintsForMergedContextConfiguration(
RuntimeHints runtimeHints, MergedContextConfiguration mergedConfig) {
RuntimeHints runtimeHints, ClassLoader classLoader, MergedContextConfiguration mergedConfig) {

// @ContextConfiguration(loader = ...)
ContextLoader contextLoader = mergedConfig.getContextLoader();
Expand All @@ -68,14 +70,14 @@ private void registerHintsForMergedContextConfiguration(
.forEach(clazz -> registerDeclaredConstructors(runtimeHints, clazz));

// @ContextConfiguration(locations = ...)
registerClasspathResources(runtimeHints, mergedConfig.getLocations());
registerClasspathResources(mergedConfig.getLocations(), runtimeHints, classLoader);

// @TestPropertySource(locations = ... )
registerClasspathResources(runtimeHints, mergedConfig.getPropertySourceLocations());
registerClasspathResources(mergedConfig.getPropertySourceLocations(), runtimeHints, classLoader);

// @WebAppConfiguration(value = ...)
if (mergedConfig instanceof WebMergedContextConfiguration webConfig) {
registerClasspathResourceDirectoryStructure(runtimeHints, webConfig.getResourceBasePath());
registerClasspathResourceDirectoryStructure(webConfig.getResourceBasePath(), runtimeHints);
}
}

Expand All @@ -94,16 +96,20 @@ private void registerDeclaredConstructors(RuntimeHints runtimeHints, Class<?> ty
runtimeHints.reflection().registerType(type, INVOKE_DECLARED_CONSTRUCTORS);
}

private void registerClasspathResources(RuntimeHints runtimeHints, String... locations) {
Arrays.stream(locations)
.filter(location -> location.startsWith(CLASSPATH_URL_PREFIX))
.map(this::cleanClasspathResource)
.forEach(runtimeHints.resources()::registerPattern);
private void registerClasspathResources(String[] paths, RuntimeHints runtimeHints, ClassLoader classLoader) {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader);
Arrays.stream(paths)
.filter(path -> path.startsWith(CLASSPATH_URL_PREFIX))
.map(resourceLoader::getResource)
.forEach(resource -> RuntimeHintsUtils.registerResourceIfNecessary(runtimeHints, resource));
}

private void registerClasspathResourceDirectoryStructure(RuntimeHints runtimeHints, String directory) {
private void registerClasspathResourceDirectoryStructure(String directory, RuntimeHints runtimeHints) {
if (directory.startsWith(CLASSPATH_URL_PREFIX)) {
String pattern = cleanClasspathResource(directory);
String pattern = directory.substring(CLASSPATH_URL_PREFIX.length());
if (pattern.startsWith(SLASH)) {
pattern = pattern.substring(1);
}
if (!pattern.endsWith(SLASH)) {
pattern += SLASH;
}
Expand All @@ -112,12 +118,4 @@ private void registerClasspathResourceDirectoryStructure(RuntimeHints runtimeHin
}
}

private String cleanClasspathResource(String location) {
location = location.substring(CLASSPATH_URL_PREFIX.length());
if (location.startsWith(SLASH)) {
location = location.substring(1);
}
return location;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
import org.apache.commons.logging.LogFactory;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.support.RuntimeHintsUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.lang.NonNull;
Expand Down Expand Up @@ -148,10 +150,10 @@ public void afterTestMethod(TestContext testContext) {
@Override
public void processAheadOfTime(Class<?> testClass, RuntimeHints runtimeHints, ClassLoader classLoader) {
getSqlAnnotationsFor(testClass).forEach(sql ->
registerClasspathResources(runtimeHints, getScripts(sql, testClass, null, true)));
registerClasspathResources(getScripts(sql, testClass, null, true), runtimeHints, classLoader));
getSqlMethods(testClass).forEach(testMethod ->
getSqlAnnotationsFor(testMethod).forEach(sql ->
registerClasspathResources(runtimeHints, getScripts(sql, testClass, testMethod, false))));
registerClasspathResources(getScripts(sql, testClass, testMethod, false), runtimeHints, classLoader)));
}

/**
Expand Down Expand Up @@ -390,19 +392,12 @@ private Stream<Method> getSqlMethods(Class<?> testClass) {
return Arrays.stream(ReflectionUtils.getUniqueDeclaredMethods(testClass, sqlMethodFilter));
}

private void registerClasspathResources(RuntimeHints runtimeHints, String... locations) {
Arrays.stream(locations)
.filter(location -> location.startsWith(CLASSPATH_URL_PREFIX))
.map(this::cleanClasspathResource)
.forEach(runtimeHints.resources()::registerPattern);
}

private String cleanClasspathResource(String location) {
location = location.substring(CLASSPATH_URL_PREFIX.length());
if (location.startsWith(SLASH)) {
location = location.substring(1);
}
return location;
private void registerClasspathResources(String[] paths, RuntimeHints runtimeHints, ClassLoader classLoader) {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader);
Arrays.stream(paths)
.filter(path -> path.startsWith(CLASSPATH_URL_PREFIX))
.map(resourceLoader::getResource)
.forEach(resource -> RuntimeHintsUtils.registerResourceIfNecessary(runtimeHints, resource));
}

}

0 comments on commit 28c492c

Please sign in to comment.