diff --git a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java index 2b2242a617..23afa0b4a7 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java @@ -38,7 +38,6 @@ import java.nio.file.Paths; import java.util.*; -import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; public class ClasspathScanningLoader implements ResourceLoader { @@ -69,7 +68,8 @@ public ClasspathScanningLoader(Properties properties, String[] acceptPackages) { scanClasses(new ClassGraph().acceptPackages(acceptPackages), getClass().getClassLoader()); scanYaml(new ClassGraph().acceptPaths("META-INF/rewrite"), properties, - emptyList(), + null, + null, null); }; } @@ -93,12 +93,13 @@ public ClasspathScanningLoader(Properties properties, ClassLoader classLoader) { .overrideClassLoaders(classLoader) .acceptPaths("META-INF/rewrite"), properties, - emptyList(), - classLoader); + null, + classLoader, + null); }; } - public ClasspathScanningLoader(Path jar, Properties properties, Collection dependencyResourceLoaders, ClassLoader classLoader) { + public ClasspathScanningLoader(Path jar, Properties properties, @Nullable ResourceLoader dependencyResourceLoader, ClassLoader classLoader) { this.classLoader = classLoader; this.recipeLoader = new RecipeLoader(classLoader); @@ -109,17 +110,16 @@ public ClasspathScanningLoader(Path jar, Properties properties, Collection dependencyResourceLoaders, @Nullable ClassLoader classLoader) { + private void scanYaml(ClassGraph classGraph, Properties properties, @Nullable ResourceLoader dependencyResourceLoader, @Nullable ClassLoader classLoader, @Nullable Path targetJar) { try (ScanResult scanResult = classGraph.scan()) { List yamlResourceLoaders = new ArrayList<>(); - scanResult.getResourcesWithExtension("yml").forEachInputStreamIgnoringIOException((res, input) -> - yamlResourceLoaders.add(new YamlResourceLoader(input, res.getURI(), properties, classLoader, dependencyResourceLoaders))); - scanResult.getResourcesWithExtension("yaml").forEachInputStreamIgnoringIOException((res, input) -> - yamlResourceLoaders.add(new YamlResourceLoader(input, res.getURI(), properties, classLoader, dependencyResourceLoaders))); + scanResult.getResourcesWithExtension("yml").forEachInputStreamIgnoringIOException((res, input) -> { + if (targetJar == null || isFromJar(res.getClasspathElementURI(), targetJar)) { + yamlResourceLoaders.add(new YamlResourceLoader(input, res.getURI(), properties, classLoader, dependencyResourceLoader)); + } + }); + scanResult.getResourcesWithExtension("yaml").forEachInputStreamIgnoringIOException((res, input) -> { + if (targetJar == null || isFromJar(res.getClasspathElementURI(), targetJar)) { + yamlResourceLoaders.add(new YamlResourceLoader(input, res.getURI(), properties, classLoader, dependencyResourceLoader)); + } + }); // Extract in two passes so that the full list of recipes from all sources are known when computing recipe descriptors // Otherwise recipes which include recipes from other sources in their recipeList will have incomplete descriptors for (YamlResourceLoader resourceLoader : yamlResourceLoaders) { diff --git a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java index 793fa594a4..cbc8ad6a28 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java @@ -26,11 +26,16 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; import java.nio.file.Path; import java.util.*; import java.util.function.Function; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.Comparator.comparingInt; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toList; @@ -347,23 +352,19 @@ public Builder scanYamlResources() { */ @SuppressWarnings("unused") public Builder scanJar(Path jar, Collection dependencies, ClassLoader classLoader) { - List firstPassLoaderList = new ArrayList<>(); - for (Path dep : dependencies) { - firstPassLoaderList.add(new ClasspathScanningLoader(dep, properties, emptyList(), classLoader)); - } - - /* - * Second loader creation pass where the firstPassLoaderList is passed as the - * dependencyResourceLoaders list to ensure that we can resolve transitive - * dependencies using the loaders we just created. This is necessary because - * the first pass may have missing recipes since the full list of loaders was - * not provided. - */ - List secondPassLoaderList = new ArrayList<>(); - for (Path dep : dependencies) { - secondPassLoaderList.add(new ClasspathScanningLoader(dep, properties, firstPassLoaderList, classLoader)); + List list = new ArrayList<>(); + for (Path dependency : dependencies) { + URI uri = dependency.toUri(); + try { + list.add(uri.toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } } - return load(new ClasspathScanningLoader(jar, properties, secondPassLoaderList, classLoader), secondPassLoaderList); + // Single `ClassLoader` representing all dependencies + URLClassLoader dependencyClassLoader = new URLClassLoader(list.toArray(new URL[0]), classLoader); + ClasspathScanningLoader dependencyLoader = new ClasspathScanningLoader(properties, dependencyClassLoader); + return load(new ClasspathScanningLoader(jar, properties, dependencyLoader, classLoader), singletonList(dependencyLoader)); } @SuppressWarnings("unused") diff --git a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java index 2b3edad25e..34b5ef295d 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java @@ -58,7 +58,7 @@ public class YamlResourceLoader implements ResourceLoader { private final ObjectMapper mapper; - private final Collection dependencyResourceLoaders; + private final @Nullable ResourceLoader dependencyResourceLoader; @Nullable private Map> contributors; @@ -115,7 +115,7 @@ public YamlResourceLoader(InputStream yamlInput, URI source, Properties properties, @Nullable ClassLoader classLoader) throws UncheckedIOException { - this(yamlInput, source, properties, classLoader, emptyList()); + this(yamlInput, source, properties, classLoader, null); } /** @@ -126,15 +126,15 @@ public YamlResourceLoader(InputStream yamlInput, * @param source Declarative recipe source * @param properties Placeholder properties * @param classLoader Optional classloader to use with jackson. If not specified, the runtime classloader will be used. - * @param dependencyResourceLoaders Optional resource loaders for recipes from dependencies + * @param dependencyResourceLoader Optional resource loader for recipes from dependencies * @throws UncheckedIOException On unexpected IOException */ public YamlResourceLoader(InputStream yamlInput, URI source, Properties properties, @Nullable ClassLoader classLoader, - Collection dependencyResourceLoaders) throws UncheckedIOException { - this(yamlInput, source, properties, classLoader, dependencyResourceLoaders, jsonMapper -> { + @Nullable ResourceLoader dependencyResourceLoader) throws UncheckedIOException { + this(yamlInput, source, properties, classLoader, dependencyResourceLoader, jsonMapper -> { }); } @@ -146,16 +146,16 @@ public YamlResourceLoader(InputStream yamlInput, * @param source Declarative recipe source * @param properties Placeholder properties * @param classLoader Optional classloader to use with jackson. If not specified, the runtime classloader will be used. - * @param dependencyResourceLoaders Optional resource loaders for recipes from dependencies + * @param dependencyResourceLoader Optional resource loader for recipes from dependencies * @param mapperCustomizer Customizer for the ObjectMapper * @throws UncheckedIOException On unexpected IOException */ public YamlResourceLoader(InputStream yamlInput, URI source, Properties properties, @Nullable ClassLoader classLoader, - Collection dependencyResourceLoaders, + @Nullable ResourceLoader dependencyResourceLoader, Consumer mapperCustomizer) { this.source = source; - this.dependencyResourceLoaders = dependencyResourceLoaders; + this.dependencyResourceLoader = dependencyResourceLoader; this.mapper = ObjectMappers.propertyBasedMapper(classLoader); this.recipeLoader = new RecipeLoader(classLoader); @@ -409,7 +409,7 @@ public Collection listRecipeDescriptors(Collection ext externalRecipes.stream(), internalRecipes.stream() ), - dependencyResourceLoaders.stream().flatMap(rl -> rl.listRecipes().stream()) + dependencyResourceLoader != null ? dependencyResourceLoader.listRecipes().stream() : Stream.empty() ).collect(toList()); List recipeDescriptors = new ArrayList<>(); diff --git a/rewrite-core/src/test/java/org/openrewrite/config/ClasspathScanningLoaderTest.java b/rewrite-core/src/test/java/org/openrewrite/config/ClasspathScanningLoaderTest.java index db9d644593..5535aedc7b 100644 --- a/rewrite-core/src/test/java/org/openrewrite/config/ClasspathScanningLoaderTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/config/ClasspathScanningLoaderTest.java @@ -36,6 +36,7 @@ import java.util.jar.JarOutputStream; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; class ClasspathScanningLoaderTest { @@ -61,7 +62,7 @@ void testInheritanceAcrossJars() throws IOException { ); Environment fullEnv = Environment.builder() - .scanJar(concreteJar, emptyList(), fullClassLoader) + .scanJar(concreteJar, singletonList(coreJar), fullClassLoader) .build(); assertThat(fullEnv.listRecipes()).hasSize(1); diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java index 65407bf15e..daa08912ca 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java @@ -38,7 +38,6 @@ import java.util.function.Function; import java.util.function.Supplier; -import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -150,7 +149,7 @@ public RecipeSpec recipeFromResources(String... activeRecipes) { private static Recipe recipeFromInputStream(InputStream yaml, String... activeRecipes) { return Environment.builder() - .load(new YamlResourceLoader(yaml, URI.create("rewrite.yml"), new Properties(), null, emptyList(), + .load(new YamlResourceLoader(yaml, URI.create("rewrite.yml"), new Properties(), null, null, mapper -> mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES))) .build() .activateRecipes(activeRecipes);