Skip to content

Latest commit

 

History

History
393 lines (299 loc) · 12.9 KB

howto.adoc

File metadata and controls

393 lines (299 loc) · 12.9 KB

How To

Check Preconditions

Preconditions that must be met to allow a successful migration can be checked before scanning an application. This allows a user to be informed about unmet preconditions that would prevent a successful migration before the sometimes lengthy parsing is done.

To add a new precondition check you need to provide a component extending abstract PreconditionCheck class and return the result of the precondition check as PreconditionCheckResult.

@Component
public class MyPreconditionCheck extends PreconditionCheck {
    @Override
    public PreconditionCheckResult verify(Path projectRoot, List<Resource> projectResources) {
        // verify precondition is met...
    }
}

This class must be placed under org.springframework.sbm to be picked up by component scan and gets executed after scanning the project but before parsing resources into an AST. If one of the preconditions fails (ResultState.WARN or ResultState.FAILED), the scan is interrupted and a message is shown to the user.

Implement Actions

First step on your journey to implement a new recipe will be the implementation of an Action. Every Action that needs access to the ProjectContext to modify resources should extend AbstractAction.

class MyAction extends AbstractAction {
    void apply(ProjectContext context) {
        // analyse and modify AST
    }
}

Use [TestProjectContext] to test your Action.

Display Progress

A long running action can leave the user without any information about the progress of the migration.

The Action interface defines methods to display progress information to the user. There are two types of rendered information, process and log messages.

Process

startStep(String) starts a new sub routine.

stopStep() stops the last sub routine

Log Messages

logEvent(String) logs a message inside a routine

When an action starts, a first process (the action) is automatically started. It begins with the action and ends with success or error. If no other progress change is reported during the execution of the action, a loader is rendered during the

.    My Action
..   My Action
[..] My Action
    .    Sub Routine
    ..   Sub Routine
    [ok] Sub Routine
[ok] My Action

Creating Recipes

Recipes bundle a set of Actions for a migration. They can be declared programmatically as Spring beans or declarative using yaml syntax. The main difference is that yaml recipes can be provided to SBM without code changes on startup allowing to add new, declarative recipes without recompilation while the bean definition is arguably easier to use.

Declarative recipes

Declarative recipes must be placed under src/main`resources/recipes.
See initialize-spring-boot-migration.yaml for a recipe in YAML syntax

Recipe beans

A recipe can be defined as Spring bean using @Bean annotated method, see MigrateMuleToBoot.java for a recipe declared as Spring bean.

Note
Remember to provide a description to all Actions to display the description to the user when the Action is applied.

Using OpenRewrite Recipes

Programmatically

ProjectContext context = ...
String annotationPattern = "@java.lang.Deprecated";
org.openrewrite.java.RemoveAnnotation rewriteRecipe = new RemoveAnnotation(annotationPattern);
context.getProjectJavaSources().apply(rewriteRecipe);

In SBM YAML

Using Declarative OpenRewrite YAML

Use org.springframework.sbm.engine.recipe.OpenRewriteDeclarativeRecipeAdapter to use embedded OpenRewrite YAML syntax in SBM YAML to run a declarative OpenRewrite recipe as SBM action.

- name: test-recipe
  description: "Remove @Deprecated annotations"
  condition:
    type: org.springframework.sbm.common.migration.conditions.TrueCondition
  actions:
    - type: org.springframework.sbm.engine.recipe.OpenRewriteDeclarativeRecipeAdapter
      description: "Use a OpenRewrite recipe to remove @Deprecated annotations"
      openRewriteRecipe: |-
        type: specs.openrewrite.org/v1beta/recipe
        name: org.openrewrite.java.RemoveAnnotation
        displayName: "Remove @Deprecated annotation"
        description: "Remove @Deprecated annotation"
        recipeList:
          - org.openrewrite.java.RemoveAnnotation:
              annotationPattern: "@java.lang.Deprecated"
By Name

Use org.springframework.sbm.engine.recipe.OpenRewriteNamedRecipeAdapter to run an OpenRewrite recipe by name as SBM action. Here an OpenRewrite recipe with name org.springframework.sbm.dummy.RemoveDeprecatedAnnotation must exist on the classpath and will be executed as action in test-recipe.

- name: test-recipe
  description: "Remove @Deprecated annotations"
  condition:
    type: org.springframework.sbm.common.migration.conditions.TrueCondition
  actions:
    - type: org.springframework.sbm.engine.recipe.OpenRewriteNamedRecipeAdapter
      description: "Call a OpenRewrite recipe to remove @Deprecated annotations"
      openRewriteRecipeName: org.springframework.sbm.dummy.RemoveDeprecatedAnnotation

In SBM Java code

Using Declarative OpenRewrite YAML
@Configuration
public class SomeRecipe {

    @Bean
    Recipe someRecipe(RewriteRecipeLoader rewriteRecipeLoader, RewriteMigrationResultMerger resultMerger) {
        return Recipe.builder()
                ...
                .action(
                        OpenRewriteDeclarativeRecipeAdapter.builder()
                                .rewriteRecipeLoader(rewriteRecipeLoader)
                                .resultMerger(resultMerger)
                                .openRewriteRecipe(
                                    "type: specs.openrewrite.org/v1beta/recipe\n" +
                                    "name: org.openrewrite.java.RemoveAnnotation\n" +
                                    "displayName: \"Remove @Deprecated annotation\"\n" +
                                    "description: \"Remove @Deprecated annotation\"\n" +
                                    "recipeList:\n" +
                                    "  - org.openrewrite.java.RemoveAnnotation:\n" +
                                    "      annotationPattern: \"@java.lang.Deprecated\"\n"
                                )
                                .build())
                ...
                .build();
    }
}
By Name
@Configuration
public class SomeRecipe {

    @Bean
    Recipe someRecipe(RewriteRecipeLoader rewriteRecipeLoader, RewriteMigrationResultMerger resultMerger) {
        return Recipe.builder()
                ...
                .action(
                        OpenRewriteNamedRecipeAdapter.builder()
                                .rewriteRecipeLoader(rewriteRecipeLoader)
                                .resultMerger(resultMerger)
                                .openRewriteRecipeName(
                                    "org.springframework.sbm.dummy.RemoveDeprecatedAnnotation"
                                )
                                .build())
                ...
                .build();
    }
}

Create a Condition

Every Action and every Recipe must have a Condition which defines if the Recipe or Action is applicable. Therefore the Condition interface must be implemented which defines a evaluate(ProjectContext) method which must return true if the Recipe or Action is applicable and false otherwise. A condition should only read from the ProjectContext and never modify the AST.

public class MyCondition implements Condition {
    @Override
    public boolean evaluate(ProjectContext context) {
        // analyze ProjectContext to evaluate condition
    }
}

Single and Multi module projects

Projects can come as single module or multi-module project. Working with single module projects is significantly easier because only one BuildFile exists. With multi-module projects the application modules play a central role and there must be means to select a module.

Application Modules

A common multi-module project has a root modules which consists of one or more child modules which again can consist of multiple child modules and so on.

  • The root module

  • The application module(s), bundle all modules of an application and define the composition of deployable artifact buulding a runnable application, e.g. a war module

  • The component module(s), define reusable components which are not deployable in isolation

public void apply(ProjectContext context) {
    Modules modules = context.getModules();
    Module module = modules.getModule("path/of/module");
    boolean isMultiModule = modules.isMultiModuleProject();
    Module root = modules.getRootModule(); // type="pom" or type="ear"
    modules.getApplicationModules(); // type="war", containing "main" method
    modules.getComponentModules(); // type="jar" without main method

    List<Module> parentModules = module.getParentModules();
    List<Module> subModules = module.getSubModules();
}
Finding the Module containing a resource
Note
Only "Maven" main and test are used as source sets and any settings in the pom.xml or otherwise are ignored.
ProjectResource pr = ...
Optional<Module> module = context.getModules.findModuleContaining(pr.getAbsolutePath());

BuildFile and Dependencies

The buildfiles of the scanned project are represented by BuildFile. The BuildFile API offers methods to read and modify the buildfile. BuildFiles can be retrieved through the ProjectContext.

    // Retrieve the root build file
    BuildFile rootBuildFile = projectContext.getBuildFile();

Adding a Dependency

public void apply(ProjectContext context) {
    // ...get buildFile for module
    BuildFile buildFile = context...
    Dependency dependency = Dependency.builder()
                                .groupId("...")
                                .artifactId("...")
                                .version("...")
                                .scope("test")
                                .build();
    buildFile.addDependency(dependency);
}

Removing a Dependency

Adding Dependency Exclusion

Migrating Java Code

Access all JavaSources

Adding annotations

Removing annotations

Modifying annotations

Migrating Methods

Add a new JavaSource

A new JavaSource must be added to a JavaSourceSet of a given ApplicationModule. The default JavaSourceSets are 'main' (src/main/java) and 'test' (src/test/java).

Example: Adding a new Java class to the 'main' source set of an ApplicationModule

public void apply(ProjectContext context) {

    ApplicationModule targetModule = ... // retrieve target module

    String javaCode =
        "package com.example.foo;\n" +
        "public class Bar {}";

    Path projectRootDirectory = context.getProjectRootDirectory();

    targetModule.getMainJavaSourceSet().addJavaSource(projectRootDirectory, sourceFolder, src, packageName);
}

OpenRewrite Recipe and Visitor

Use Freemarker templates

Add this snippet to your Action to use freemarker

public class MyAction extends AbstractAction {

    @Autowired
    @JsonIgnore
    @Setter
    private Configuration configuration;

    // ...
}

and place your template under src/main/resources/templates

Example: using Freemarker template in Action

Map<String, String> params = new HashMap<>();
params.put("groupId", "com.example.change");
params.put("artifactId", projectName);
params.put("version", "0.1.0-SNAPSHOT");

StringWriter writer = new StringWriter();
try {
    Template template = configuration.getTemplate("minimal-pom-xml.ftl");
    template.process(params, writer);
} catch (TemplateException | IOException e) {
    throw new RuntimeException(e);
}
String src = writer.toString();

Migrate Multi Module Projects

Access a Module’s JavaSources

Specialized Resources

Create a Finder to access other resources

The ProjectContext only offers direct access to Java and BuildFile resources. To access other resources the concept of Finders exists. A Finder implements the ResourceFinder interface.

public interface ProjectResourceFinder<T> {
    T apply(ProjectResourceSet projectResourceSet);
}

These Finders can than be provided to the search(…​) method of ProjectContext. The ProjectContext will provide the ProjectResourceSet to the Finder and the Finder can then filter/search

Manipulate Spring Boot properties

Create a specialized Resource