Skip to content

Latest commit

 

History

History
311 lines (238 loc) · 9.55 KB

File metadata and controls

311 lines (238 loc) · 9.55 KB

sbm-support-rewrite

The sbm-support-rewrite project provides Spring beans classes to parse a given project to an OpenRewrite abstract syntax tree (AST) which can then be used to run OpenRewrite recipes that were discovered on the classpath.

Components

The following components can be used to parse a project, run recipes and write changes back to the filesystem. These components are provided as Spring beans and can be injected into other Spring beans that require them.

Example: Inject RewriteProjectParser into your Spring bean

@Autowired
RewriteProjectParser parser;

ProjectScanner

Scan a given path to a list of resources using filter definitions provided as application properties.

RewriteProjectParser

Parses a project to OpenRewrite’s AST representation.

RewriteExecutionContext

OpenRewrite’s ExecutionContext gets initialized during parsing. This ExecutionContext is required for some recipes and inner workings of OpenRewrite.

RecipeDiscovery

Discover OpenRewrite recipes on classpath

ProjectResourceSet

Abstraction of OpenRewrite SourceFiles that allows execution of recipes against the SourceFiles while synchronizing changes with the underlying list of SourceFiles.

ProjectResourceSetSerializer

Write back the in-memory changed SourceFiles from the ProjectResourceSet to the filesystem.

Getting started

Adding the dependency

Currently only SNAPSHOT releases are available from https://repo.spring.io. To access these, a repository must be added.

<repositories>
    <repository>
        <id>spring-snapshot</id>
        <url>https://repo.spring.io/snapshot</url>
        <releases>
            <enabled>false</enabled>
        </releases>
    </repository>
</repositories>

Then the dependency can be retrieved.

<dependency>
    <groupId>org.springframwork.experimental</groupId>
    <artifactId>sbm-support-rewrite</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>

(Optional) Scan a project

ProjectScanner scans a given Path to a list of Resources. It filters out resources and directories matching any of the ignore patterns in parser.ignoredPathPatterns.

Parse a project

RewriteProjectParser parses a project to OpenRewrite AST.

The provided parse(Path) method can be used to parse a project under a given Path to OpenRewrite AST.

Path baseDir = ...
List<SourceFile> ast = parser.parse(baseDir);

ExecutionContext

OpenRewrite’s ExecutionContext is populated during parsing and the settings might be required for recipes executed later. The ExecutionContext is provided as scoped Spring bean and can be injected into other Spring beans. It has the same scope as the parsing and a new instance is automatically created with every parse.

Note
The ExecutionContext should always be injected and should not be created programmatically.

Discover and run recipes

OpenRewrite recipes can be discovered from classpath with RewriteRecipeDiscovery which is provided as Spring bean.

@Autowired
RewriteRecipeDiscovery discovery;

@Autowired
ExecutionContext ctx;
...
Recipe recipe = discovery.getRecipe("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1");
RecipeRun recipe = recipe.run(new InMemoryLargeSourceSet(ast), ctx));

Use ProjectResourceSet

A successful recipe run will return the modified SourceFiles. Before another recipe can be applied to the AST the changed SourceFiles need to be merged into the original list of SourceFiles (the AST). The ProjectResourceSet provides this capability.

@Component
public class SomeClass {

    @Autowired
    ProjectResourceSetFactory factory;

    @Autowired
    RewriteProjectParser parser;

    void method() {
        Recipe r1 = ...
        Recipe r2 = ...
        RewriteProjectParsingResult parsingResult = parser.parse(baseDir);
        List<SourceFile> sourceFiles = parsingResult.sourceFiles();
        ProjectResourceSet projectResourceSet = factory.create(baseDir, sourceFiles);
        projectResourceSet.apply(r1); // internally changes get merged back to AST
        projectResourceSet.apply(r2); // r2 applied against the AST with changes from r1
    }
}

Write changes back to filesystem

The ProjectResourceSetSerializer can be used to write all changes (modify, delete, add) in ProjectResourceSet to the filesystem.

@Autowired
private ProjectResourceSetSerializer serializer;
...
public void method() {
    ...
    serializer.writeChanges(projectResourceSet);
}

Listen to ParserEvents

ParserEvents get published during parsing. The events can be used to provide progress information to users. This is especially useful when parsing large projects where parsing can take some time.

  • StartedParsingProjectEvent - Gets published when the parsing started

  • ParsedResourceEvent - Gets published after every parsed pom or Java file

  • SuccessfullyParsedProjectEvent - Gets published when the parsing was successful

@EventListener(ParsedResourceEvent.class)
public void onParsedResourceEvent(ParsedResourceEvent event) {
    Parser.Input input = event.input();
    SourceFile sourceFile = event.sourceFile();
    log("parsed %s to %s".formatted(input.getRelativePath(), sourceFile.getClass().getName()));
}

Configuration

Some behaviour can be configured through application properties or by providing custom Spring beans.

Maven Artifact Cache

OpenRewrite uses a MavenArtifactCache to store downloaded jar dependencies. The provided MavenArtifactCache bean tries to retrieve jars from local Maven cache ~/.m2/repository first. If the dependency doesn’t exist it is searched under ~/.rewrite/cache/artifacts and if it doesn’t exist it is downloaded to this dir.

@Bean
MavenArtifactCache mavenArtifactCache() {
    Path userHome = Path.of(System.getProperty("user.home"));
    Path localMavenRepo = userHome.resolve(".m2/repository");
    Path localRewriteRepo = userHome.resolve(".rewrite/cache/artifacts");
    return new LocalMavenArtifactCache(localMavenRepo)
                .orElse(localRewriteRepo));
}

Custom Maven Artifact Cache

The provided cache configuration can be replaced with a custom bean.

@Bean
MavenArtifactCache mavenArtifactCache() {
    return new CustomMavenArtifactCache();
}

Maven Pom Cache

OpenRewrite downloads Maven Pom files to resolve dependencies. The pom files get cached and the cache depends on the system.

  • 32-Bit systems use the InMemoryPomCache.

  • 64-Bit systems use the RocksdbMavenPomCache.

Pom Cache Properties

Name Description Default Value

parser.isPomCacheEnabled

If the flag is set to false, only the default, in-memory cache is used.

false

parser.pomCacheDirectory

Defines the cache dir for RocksdbMavenPomCache when parser.isPomCacheEnabled is true

~/.rewrite-cache

Custom Pom Cache

A custom MavenPomCache implementation can be provided through a custom Spring bean declaration.

@Bean
public MavenPomCache mavenPomCache() {
    return new CustomMavenPomCache();
}

Example

Example code showing how to apply OpenRewrite’s UpgradeSpringBoot_3_1 recipe

package com.example;

import org.openrewrite.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.sbm.parsers.ProjectScanner;
import org.springframework.sbm.parsers.RewriteProjectParser;
import org.springframework.sbm.parsers.RewriteProjectParsingResult;
import org.springframework.sbm.project.resource.ProjectResourceSet;
import org.springframework.sbm.project.resource.ProjectResourceSetFactory;
import org.springframework.sbm.project.resource.ProjectResourceSetSerializer;
import org.springframework.sbm.recipes.RewriteRecipeDiscovery;

import java.nio.file.Path;
import java.util.List;

@SpringBootApplication
public class BootUpgrade implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(BootUpgrade.class, args);
    }

    @Autowired
    ProjectScanner scanner;
    @Autowired
    RewriteProjectParser parser;
    @Autowired
    RewriteRecipeDiscovery discovery;
    @Autowired
    ProjectResourceSetSerializer serializer;
    @Autowired
    ProjectResourceSetFactory factory;

    @Override
    public void run(String... args) throws Exception {

        String path  = "demo-spring-song-app";
        Path baseDir = Path.of(path ).toAbsolutePath().normalize();
        System.out.println(baseDir);
        if(!baseDir.toFile().exists() || !baseDir.toFile().isDirectory()) {
            throw new IllegalArgumentException("Given path '%s' does not exist or is not a directory.".formatted(path));
        }

        // parse
        RewriteProjectParsingResult parsingResult = parser.parse(baseDir);
        List<SourceFile> sourceFiles = parsingResult.sourceFiles();

        // create ProjectResourceSet
        ProjectResourceSet projectResourceSet = factory.create(baseDir, sourceFiles);

        // find recipe
        String recipeName = "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1";
        List<Recipe> recipes = discovery.discoverRecipes();
        Recipe recipe = findRecipe(recipes, recipeName);

        // apply recipe
        projectResourceSet.apply(recipe);

        // write changes to fs
        serializer.writeChanges(projectResourceSet);
    }
}