openrewrite / openrewrite/rewrite

Rewrite fails to load `classpath.tsv.gz` resources from additional GAVs of recipes when used as a library

Open
#6,885 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
3.7k
Forks
571
Avg merge
13h 12m
Merged PRs (30d)
261

Description

Issue

When we execute openrewrite part of a java quarkus application where the openrewrite GAVs have been loaded part of the AppClassLoader, then we hit a problem as rewrite during the execution of some recipes cannot load the classpath.tsv.gz of by example org.openrewrite.recipe:rewrite-java-dependencies:1.51.1 if we use an URLClassloader having as parent the AppClassloader.

The problem takes place when OpenRewrite runs a recipe like org.openrewrite.java.ReplaceAnnotation where they access on the JavaParser class the method classpathFromResources(Context,String ) able to search about such a resource.

As you can see hereafter the current code uses as ClassLoader the one having loaded the JavaParser class and not at all the URLClassloader created using the Environmen Builder and Environment.Collection<? extends ResourceLoader> resourceLoaders

// org.openrewrite.java.ReplaceAnnotation
a = JavaTemplate.builder(annotationTemplateToInsert)
                        .javaParser(classpathResourceName == null ?
        JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()) :
        JavaParser.fromJavaVersion().classpathFromResources(ctx, classpathResourceName)) // HERE
...
// JavaParser class
public B classpathFromResources(ExecutionContext ctx, String... classpath) {
    this.artifactNames = emptyList();
    this.classpath = dependenciesFromResources(ctx, classpath);
    return (B) this;
}
...
static List<Path> dependenciesFromResources(ExecutionContext ctx, String... artifactNamesWithVersions) {
    if (artifactNamesWithVersions.length == 0) {
        return emptyList();
    }
    List<Path> artifacts = new ArrayList<>(artifactNamesWithVersions.length);
    Set<String> missingArtifactNames = new LinkedHashSet<>(Arrays.asList(artifactNamesWithVersions));
    List<String> availableArtifacts = new ArrayList<>();

    TypeTable typeTable = TypeTable.fromClasspath(ctx, missingArtifactNames);
    ...

// TypeTable class    
    public static @Nullable TypeTable fromClasspath(ExecutionContext ctx, Collection<String> artifactNames) {
        try {
            ClassLoader classLoader = findCaller().getClassLoader();
            // The ClassLoader we got it here is the AppClassLoader !!

FYI: I created a project showing the issue here: https://github.com/ch007m/tsv-classloading-issue

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked reproducer and trace JavaParser.classpathFromResources through dependenciesFromResources to TypeTable.fromClasspath. Examine how the caller ClassLoader is selected when an Environment-created URLClassLoader has an AppClassLoader parent. Done means the recipe can load the additional GAV's classpath.tsv.gz resource in that setup.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.