Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring Batch recipe for MigrateStepBuilderFactory #284

Merged
merged 32 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
719d80d
springbatch recipe configuration
desprez Feb 7, 2023
8d5bab0
Merge branch 'main' of https://github.com/desprez/rewrite-spring
desprez Feb 7, 2023
1a9ffad
new spring-batch 5.x migration recipes
desprez Feb 7, 2023
0bc63a9
remove trace
desprez Feb 7, 2023
af74189
add MigrateItemWriterWrite recipe
desprez Feb 8, 2023
0e08b5c
Tabs to spaces
sambsnyd Feb 9, 2023
8a25a25
Get MigrateItemWriterWrite tests passing
sambsnyd Feb 9, 2023
703b335
Preserve annotations, add @Override if missing
sambsnyd Feb 9, 2023
ac94b87
Merge remote-tracking branch 'origin/main' into desprez/main
timtebeek Feb 9, 2023
dc4aba4
Merge branch 'openrewrite:main' into main
desprez Feb 9, 2023
dc72f39
add missing MigrateItemWriterWrite
desprez Feb 10, 2023
9a71547
MigrateStepBuilderFactory recipe draft
desprez Feb 10, 2023
5619fca
Merge remote-tracking branch 'origin/main' into desprez/main
timtebeek Apr 24, 2023
05059d9
Merge branch 'main' into main
timtebeek Jun 29, 2023
d40a239
Merge branch 'main' into main
timtebeek Sep 14, 2023
396ff53
Update to use Rewrite 8
timtebeek Sep 14, 2023
df302ce
Merge branch 'main' into main
timtebeek Nov 20, 2023
8774e0b
Merge branch 'main' into main
timtebeek Feb 10, 2024
1ef2cc8
Apply automated suggestions
timtebeek Feb 10, 2024
ef23eaf
Apply suggestions from code review
timtebeek Feb 10, 2024
3b19e1c
Pull forward method declaration change
timtebeek Feb 10, 2024
531dad7
Merge branch 'main' into main
timtebeek Feb 25, 2024
7f97612
Merge branch 'main' into main
timtebeek May 22, 2024
2fb1404
Merge branch 'main' into main
timtebeek Jul 10, 2024
97ee63a
Merge branch 'main' into main
timtebeek Aug 2, 2024
d8d9164
Fix missing types on original method arguments
timtebeek Aug 2, 2024
6bbf083
Split visitors to fix first of the tests
timtebeek Aug 2, 2024
f326483
Fixes around empty parameters
timtebeek Aug 2, 2024
f970497
Merge branch 'main' into main
timtebeek Aug 2, 2024
f730a48
Merge branch 'main' into main
timtebeek Aug 11, 2024
fa0eb6d
Finishing touches
timtebeek Aug 11, 2024
97d3062
Make `MigrateStepBuilderFactory` part of Batch 5.0
timtebeek Aug 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.spring.batch;

import org.openrewrite.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.*;
import org.openrewrite.java.search.FindMethods;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Space;
import org.openrewrite.java.tree.Statement;
import org.openrewrite.java.tree.TypeUtils;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class MigrateStepBuilderFactory extends Recipe {

private static final String STEP_BUILDER_FACTORY_GET = "org.springframework.batch.core.configuration.annotation.StepBuilderFactory get(java.lang.String)";

@Override
public String getDisplayName() {
return "Migrate `StepBuilderFactory` to `StepBuilder`";
}

@Override
public String getDescription() {
return "`StepBuilderFactory` was deprecated in spring-batch 5.x. It is replaced by `StepBuilder`.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesMethod<>(STEP_BUILDER_FACTORY_GET),
new JavaVisitor<ExecutionContext>() {
@Override
public J visit(@Nullable Tree tree, ExecutionContext ctx) {
tree = new AddJobRepositoryVisitor().visit(tree, ctx);
return new NewStepBuilderVisitor().visit(tree, ctx);
}
}
);
}

private static class AddJobRepositoryVisitor extends JavaIsoVisitor<ExecutionContext> {
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDeclaration, ExecutionContext ctx) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDeclaration, ctx);

// Remove StepBuilderFactory field if StepBuilderFactory.get(..) is used further down
if (!FindMethods.find(classDeclaration, STEP_BUILDER_FACTORY_GET).isEmpty()) {
cd = cd.withBody(cd.getBody().withStatements(ListUtils.map(cd.getBody().getStatements(), statement -> {
if (statement instanceof J.VariableDeclarations
&& ((J.VariableDeclarations) statement).getTypeExpression() != null) {
if (TypeUtils.isOfClassType(((J.VariableDeclarations) statement).getTypeExpression().getType(),
"org.springframework.batch.core.configuration.annotation.StepBuilderFactory")) {
return null;
}
}
return statement;
})));
}

return cd;
}

@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration md, ExecutionContext ctx) {
// Add JobRepository parameter to method if StepBuilderFactory.get(..) is used further down
if (!FindMethods.find(md, STEP_BUILDER_FACTORY_GET).isEmpty()) {
List<Object> params = md.getParameters().stream()
.filter(j -> !(j instanceof J.Empty) && !isJobBuilderFactoryParameter(j))
.collect(Collectors.toList());

if (params.isEmpty() && md.isConstructor()) {
//noinspection DataFlowIssue
return null;
}

if (md.getParameters().stream().noneMatch(this::isJobRepositoryParameter) && !md.isConstructor()) {
maybeAddImport("org.springframework.batch.core.repository.JobRepository");
boolean parametersEmpty = md.getParameters().isEmpty() || md.getParameters().get(0) instanceof J.Empty;
J.VariableDeclarations vdd = JavaTemplate.builder("JobRepository jobRepository")
.contextSensitive()
.imports("org.springframework.batch.core.repository.JobRepository")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "spring-batch-core-5.+"))
.build()
.<J.MethodDeclaration>apply(getCursor(), md.getCoordinates().replaceParameters())
.getParameters().get(0).withPrefix(parametersEmpty ? Space.EMPTY : Space.SINGLE_SPACE);
if (parametersEmpty) {
md = md.withParameters(Collections.singletonList(vdd))
.withMethodType(md.getMethodType()
.withParameterTypes(Collections.singletonList(vdd.getType())));
} else {
md = md.withParameters(ListUtils.concat(md.getParameters(), vdd))
.withMethodType(md.getMethodType()
.withParameterTypes(ListUtils.concat(md.getMethodType().getParameterTypes(), vdd.getType())));
}
}
}

return super.visitMethodDeclaration(md, ctx);
}

private boolean isJobRepositoryParameter(Statement statement) {
return statement instanceof J.VariableDeclarations
&& TypeUtils.isOfClassType(((J.VariableDeclarations) statement).getType(),
"org.springframework.batch.core.repository.JobRepository");
}

private boolean isJobBuilderFactoryParameter(Statement statement) {
return statement instanceof J.VariableDeclarations
&& TypeUtils.isOfClassType(((J.VariableDeclarations) statement).getType(),
"org.springframework.batch.core.configuration.annotation.StepBuilderFactory");
}
}

private static class NewStepBuilderVisitor extends JavaVisitor<ExecutionContext> {
final MethodMatcher STEP_BUILDER_FACTORY_MATCHER = new MethodMatcher(STEP_BUILDER_FACTORY_GET);

@Override
public J visitMethodInvocation(J.MethodInvocation mi, ExecutionContext ctx) {
if (STEP_BUILDER_FACTORY_MATCHER.matches(mi)) {
maybeAddImport("org.springframework.batch.core.step.builder.StepBuilder", false);
maybeRemoveImport("org.springframework.beans.factory.annotation.Autowired");
maybeRemoveImport("org.springframework.batch.core.configuration.annotation.StepBuilderFactory");
return JavaTemplate.builder("new StepBuilder(#{any(java.lang.String)}, jobRepository)")
.contextSensitive()
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "spring-batch-core-5.+", "spring-batch-infrastructure-5.+"))
.imports("org.springframework.batch.core.step.builder.StepBuilder")
.build()
.apply(getCursor(), mi.getCoordinates().replace(), mi.getArguments().get(0));
}
return super.visitMethodInvocation(mi, ctx);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.spring.batch;

import org.junit.jupiter.api.Test;
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
import org.openrewrite.DocumentExample;
import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.java.JavaParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;

class MigrateStepBuilderFactoryTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new MigrateStepBuilderFactory())
.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(),
"spring-batch-core-4.3.+",
"spring-batch-infrastructure-4.3.+",
"spring-beans-4.3.30.RELEASE",
"spring-context-4.3.30.RELEASE"
));
}

@DocumentExample
@Test
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
void replaceStepBuilderFactoryWithTasket() {
// language=java
rewriteRun(
java(
"""
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

class MyJobConfig {

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
Step myStep(Tasklet myTasklet) {
return this.stepBuilderFactory.get("myStep")
.tasklet(myTasklet)
.build();
}
}
""",
"""
import org.springframework.batch.core.Step;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.context.annotation.Bean;

class MyJobConfig {

@Bean
Step myStep(Tasklet myTasklet, JobRepository jobRepository) {
return new StepBuilder("myStep", jobRepository)
.tasklet(myTasklet)
.build();
}
}
"""
)
);
}

@Test
void replaceStepBuilderFactoryWithChunk() {
// language=java
rewriteRun(
java(
"""
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

class MyJobConfig {

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
Step myStep() {
return this.stepBuilderFactory.get("myStep")
.<String, String> chunk(10)
.reader(reader())
.writer(writer())
.build();
}

private ItemWriter<String> writer() {
return null;
}

private ItemReader<String> reader() {
return null;
}
}
""",
"""
import org.springframework.batch.core.Step;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.context.annotation.Bean;

class MyJobConfig {

@Bean
Step myStep(JobRepository jobRepository) {
return new StepBuilder("myStep", jobRepository)
.<String, String> chunk(10)
.reader(reader())
.writer(writer())
.build();
}

private ItemWriter<String> writer() {
return null;
}

private ItemReader<String> reader() {
return null;
}
}
"""
)
);
}

@Test
void replaceStepBuilderFactoryWithCompletionPolicyChunk() {
// language=java
rewriteRun(
java(
"""
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

class MyJobConfig {

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
Step myStep() {
return this.stepBuilderFactory.get("myStep")
.<String, String> chunk(completionPolicy())
.reader(reader())
.writer(writer())
.build();
}

private CompletionPolicy completionPolicy() {
return null;
}

private ItemWriter<String> writer() {
return null;
}

private ItemReader<String> reader() {
return null;
}
}
""",
"""
import org.springframework.batch.core.Step;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.context.annotation.Bean;

class MyJobConfig {

@Bean
Step myStep(JobRepository jobRepository) {
return new StepBuilder("myStep", jobRepository)
.<String, String> chunk(completionPolicy())
.reader(reader())
.writer(writer())
.build();
}

private CompletionPolicy completionPolicy() {
return null;
}

private ItemWriter<String> writer() {
return null;
}

private ItemReader<String> reader() {
return null;
}
}
"""
)
);
}
}
Loading