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.
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;
Scan a given path to a list of resources using filter definitions provided as application properties.
OpenRewrite’s ExecutionContext
gets initialized during parsing.
This ExecutionContext
is required for some recipes and inner workings of OpenRewrite.
Abstraction of OpenRewrite SourceFiles that allows execution of recipes against the SourceFiles while synchronizing changes with the underlying list of SourceFiles.
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>
ProjectScanner
scans a given Path
to a list of Resource
s.
It filters out resources and directories matching any of the ignore patterns in
parser.ignoredPathPatterns
.
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);
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. |
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));
A successful recipe run will return the modified SourceFile
s.
Before another recipe can be applied to the AST the changed SourceFile
s need to be merged into the original list of SourceFile
s (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
}
}
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);
}
ParserEvent
s 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()));
}
Some behaviour can be configured through application properties or by providing custom Spring beans.
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));
}
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
.
Name | Description | Default Value |
---|---|---|
|
If the flag is set to false, only the default, in-memory cache is used. |
|
|
Defines the cache dir for RocksdbMavenPomCache when |
|
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);
}
}