-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Spring Batch recipe for MigrateStepBuilderFactory (#284)
* springbatch recipe configuration * new spring-batch 5.x migration recipes * remove trace * add MigrateItemWriterWrite recipe * Tabs to spaces * Get MigrateItemWriterWrite tests passing * Preserve annotations, add @OverRide if missing * add missing MigrateItemWriterWrite * MigrateStepBuilderFactory recipe draft * Update to use Rewrite 8 * Apply automated suggestions * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Pull forward method declaration change * Fix missing types on original method arguments * Split visitors to fix first of the tests * Fixes around empty parameters * Finishing touches * Make `MigrateStepBuilderFactory` part of Batch 5.0 --------- Co-authored-by: Sam Snyder <[email protected]> Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
5c7ad98
commit 3706324
Showing
3 changed files
with
386 additions
and
0 deletions.
There are no files selected for viewing
153 changes: 153 additions & 0 deletions
153
src/main/java/org/openrewrite/java/spring/batch/MigrateStepBuilderFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
232 changes: 232 additions & 0 deletions
232
...hSpringBoot_3_0/java/org/openrewrite/java/spring/batch/MigrateStepBuilderFactoryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
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 | ||
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; | ||
} | ||
} | ||
""" | ||
) | ||
); | ||
} | ||
} |