Skip to content

Commit 81dced9

Browse files
Crud repository extension (#242)
closes #236 Co-authored-by: sanagaraj-pivotal <[email protected]>
1 parent 58fd23b commit 81dced9

File tree

19 files changed

+706
-749
lines changed

19 files changed

+706
-749
lines changed

applications/spring-shell/src/test/java/org/springframework/sbm/BootUpgrade_27_30_IntegrationTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
/*
2+
* Copyright 2021 - 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package org.springframework.sbm;
217

318
import org.jetbrains.annotations.NotNull;
19+
import org.junit.jupiter.api.Tag;
420
import org.junit.jupiter.api.Test;
521
import org.openrewrite.maven.MavenParser;
622
import org.openrewrite.xml.tree.Xml;
@@ -17,6 +33,7 @@ protected String getTestSubDir() {
1733
}
1834

1935
@Test
36+
@Tag("integration")
2037
void migrateSimpleApplication() {
2138
intializeTestProject();
2239

@@ -29,6 +46,22 @@ void migrateSimpleApplication() {
2946
verifyYamlConfigurationUpdate();
3047
verifyPropertyConfigurationUpdate();
3148
verifyConstructorBindingRemoval();
49+
verifyCrudRepoAddition();
50+
}
51+
52+
private void verifyCrudRepoAddition() {
53+
54+
String studentRepo = loadJavaFile("org.springboot.example.upgrade", "StudentRepo");
55+
56+
assertThat(studentRepo).isEqualTo("""
57+
package org.springboot.example.upgrade;
58+
59+
import org.springframework.data.repository.CrudRepository;
60+
import org.springframework.data.repository.PagingAndSortingRepository;
61+
62+
public interface StudentRepo extends PagingAndSortingRepository<Student<?>, Long>, CrudRepository<Student<?>, Long> {
63+
}
64+
""");
3265
}
3366

3467
private void verifyConstructorBindingRemoval() {

applications/spring-shell/src/test/resources/testcode/boot-migration-27-30/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
<groupId>io.micrometer</groupId>
2626
<artifactId>micrometer-registry-prometheus</artifactId>
2727
</dependency>
28+
<dependency>
29+
<groupId>org.springframework.boot</groupId>
30+
<artifactId>spring-boot-starter-data-jpa</artifactId>
31+
</dependency>
2832
<dependency>
2933
<groupId>org.springframework.boot</groupId>
3034
<artifactId>spring-boot-starter-test</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.springboot.example.upgrade;
2+
3+
public class Student<T> {
4+
private T name;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.springboot.example.upgrade;
2+
3+
import org.springframework.data.repository.PagingAndSortingRepository;
4+
5+
public interface StudentRepo extends PagingAndSortingRepository<Student<?>, Long> {
6+
}

components/sbm-openrewrite/src/test/java/org/openrewrite/maven/java/ChangeTypeTest.java renamed to components/sbm-openrewrite/src/test/java/org/openrewrite/java/ChangeTypeTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.openrewrite.maven.java;
17+
package org.openrewrite.java;
1818

1919
import org.junit.jupiter.api.Test;
2020
import org.openrewrite.Result;

components/sbm-recipes-boot-upgrade/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,11 @@
7171
<artifactId>aspectjweaver</artifactId>
7272
<scope>test</scope>
7373
</dependency>
74+
<dependency>
75+
<groupId>org.openrewrite</groupId>
76+
<artifactId>rewrite-test</artifactId>
77+
<version>${openrewrite.version}</version>
78+
<scope>test</scope>
79+
</dependency>
7480
</dependencies>
7581
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2021 - 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.sbm.boot.upgrade_27_30;
17+
18+
19+
import org.jetbrains.annotations.NotNull;
20+
import org.openrewrite.*;
21+
import org.openrewrite.internal.lang.Nullable;
22+
import org.openrewrite.java.JavaIsoVisitor;
23+
import org.openrewrite.java.tree.J;
24+
import org.openrewrite.java.tree.JavaType;
25+
import org.springframework.sbm.boot.upgrade_27_30.helperrecipe.ImplementTypedInterface;
26+
27+
import java.util.List;
28+
import java.util.Optional;
29+
30+
public class CrudRepositoryExtension extends Recipe {
31+
public static final String PAGING_AND_SORTING_REPOSITORY = "org.springframework.data.repository.PagingAndSortingRepository";
32+
public static final String CRUD_REPOSITORY = "org.springframework.data.repository.CrudRepository";
33+
34+
@Override
35+
public String getDisplayName() {
36+
return "Extends CrudRepository for Interfaces that extends PagingAndSortingRepository";
37+
}
38+
39+
@Override
40+
protected @Nullable TreeVisitor<?, ExecutionContext> getApplicableTest() {
41+
return new JavaIsoVisitor<>() {
42+
@Override
43+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
44+
return doesItExtendPagingAndSorting(classDecl) ? applyThisRecipe(classDecl) : ceaseVisit(classDecl);
45+
}
46+
47+
private boolean doesItExtendPagingAndSorting(J.ClassDeclaration classDecl) {
48+
if (classDecl.getType() == null) {
49+
return false;
50+
}
51+
return classDecl.getType().getInterfaces().stream()
52+
.anyMatch(impl -> impl.getFullyQualifiedName().equals(PAGING_AND_SORTING_REPOSITORY));
53+
}
54+
55+
private J.ClassDeclaration ceaseVisit(J.ClassDeclaration classDecl) {
56+
return classDecl;
57+
}
58+
59+
@NotNull
60+
private J.ClassDeclaration applyThisRecipe(J.ClassDeclaration classDecl) {
61+
return classDecl.withMarkers(classDecl.getMarkers().searchResult());
62+
}
63+
};
64+
}
65+
66+
@Override
67+
protected JavaIsoVisitor<ExecutionContext> getVisitor() {
68+
return new JavaIsoVisitor<>() {
69+
@Override
70+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
71+
72+
Optional<JavaType.FullyQualified> pagingInterface = getExtendPagingAndSorting(classDecl);
73+
if (pagingInterface.isEmpty()) {
74+
return classDecl;
75+
}
76+
List<JavaType> typeParameters = pagingInterface.get().getTypeParameters();
77+
doAfterVisit(new ImplementTypedInterface<>(classDecl, CRUD_REPOSITORY, typeParameters));
78+
return classDecl;
79+
}
80+
81+
private Optional<JavaType.FullyQualified> getExtendPagingAndSorting(J.ClassDeclaration classDecl) {
82+
if (classDecl.getType() == null) {
83+
return Optional.empty();
84+
}
85+
return classDecl.getType().getInterfaces().stream()
86+
.filter(impl -> impl.getFullyQualifiedName().equals(PAGING_AND_SORTING_REPOSITORY))
87+
.findAny();
88+
}
89+
};
90+
91+
}
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright 2021 - 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.sbm.boot.upgrade_27_30.helperrecipe;
17+
18+
import org.openrewrite.Tree;
19+
import org.openrewrite.internal.ListUtils;
20+
import org.openrewrite.internal.lang.Nullable;
21+
import org.openrewrite.java.JavaIsoVisitor;
22+
import org.openrewrite.java.tree.*;
23+
import org.openrewrite.marker.Markers;
24+
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import java.util.UUID;
28+
29+
public class ImplementTypedInterface<P> extends JavaIsoVisitor<P> {
30+
private final J.ClassDeclaration scope;
31+
private final JavaType.FullyQualified interfaceType;
32+
private final List<JavaType> typeParameters;
33+
34+
public ImplementTypedInterface(J.ClassDeclaration scope, JavaType.FullyQualified interfaceType, List<JavaType> typeParameters) {
35+
this.scope = scope;
36+
this.interfaceType = interfaceType;
37+
this.typeParameters = typeParameters;
38+
}
39+
40+
public ImplementTypedInterface(J.ClassDeclaration scope, String interfaze, List<JavaType> typeParameters) {
41+
this(scope, (JavaType.FullyQualified) JavaType.ShallowClass.build(interfaze), typeParameters);
42+
}
43+
44+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) {
45+
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, p);
46+
if (c.isScope(this.scope) && (c.getImplements() == null || c.getImplements().stream().noneMatch((f) -> {
47+
return TypeUtils.isAssignableTo(f.getType(), this.interfaceType);
48+
}))) {
49+
if (!classDecl.getSimpleName().equals(this.interfaceType.getClassName())) {
50+
this.maybeAddImport(this.interfaceType);
51+
}
52+
53+
TypeTree type = (TypeTree) TypeTree.build(classDecl.getSimpleName().equals(this.interfaceType.getClassName()) ? this.interfaceType.getFullyQualifiedName() : this.interfaceType.getClassName()).withType(this.interfaceType).withPrefix(Space.format(" "));
54+
if (typeParameters != null && !typeParameters.isEmpty() && typeParameters.stream().noneMatch(tp -> tp instanceof JavaType.GenericTypeVariable)) {
55+
type = new J.ParameterizedType(UUID.randomUUID(), Space.EMPTY, Markers.EMPTY, type, buildTypeParameters(typeParameters));
56+
}
57+
c = c.withImplements(ListUtils.concat(c.getImplements(), type));
58+
JContainer<TypeTree> anImplements = c.getPadding().getImplements();
59+
60+
assert anImplements != null;
61+
62+
if (anImplements.getBefore().getWhitespace().isEmpty()) {
63+
c = c.getPadding().withImplements(anImplements.withBefore(Space.format(" ")));
64+
}
65+
}
66+
67+
return c;
68+
}
69+
70+
@Nullable
71+
private JContainer<Expression> buildTypeParameters(List<JavaType> typeParameters) {
72+
List<JRightPadded<Expression>> typeExpressions = new ArrayList<>();
73+
74+
int index = 0;
75+
for (JavaType type : typeParameters) {
76+
Expression typeParameterExpression = (Expression) buildTypeTree(type, (index++ == 0) ? Space.EMPTY : Space.format(" "));
77+
if (typeParameterExpression == null) {
78+
return null;
79+
}
80+
typeExpressions.add(new JRightPadded<>(
81+
typeParameterExpression,
82+
Space.EMPTY,
83+
Markers.EMPTY
84+
));
85+
}
86+
return JContainer.build(Space.EMPTY, typeExpressions, Markers.EMPTY);
87+
}
88+
89+
private TypeTree buildTypeTree(@Nullable JavaType type, Space space) {
90+
if (type == null || type instanceof JavaType.Unknown) {
91+
return null;
92+
} else if (type instanceof JavaType.FullyQualified) {
93+
94+
JavaType.FullyQualified fq = (JavaType.FullyQualified) type;
95+
96+
J.Identifier identifier = new J.Identifier(Tree.randomId(),
97+
space,
98+
Markers.EMPTY,
99+
fq.getClassName(),
100+
type,
101+
null
102+
);
103+
104+
if (!fq.getTypeParameters().isEmpty()) {
105+
JContainer<Expression> typeParameters = buildTypeParameters(fq.getTypeParameters());
106+
if (typeParameters == null) {
107+
//If there is a problem resolving one of the type parameters, then do not return a type
108+
//expression for the fully-qualified type.
109+
return null;
110+
}
111+
return new J.ParameterizedType(
112+
Tree.randomId(),
113+
space,
114+
Markers.EMPTY,
115+
identifier,
116+
typeParameters
117+
);
118+
119+
} else {
120+
maybeAddImport(fq);
121+
return identifier;
122+
}
123+
} else if (type instanceof JavaType.GenericTypeVariable) {
124+
JavaType.GenericTypeVariable genericType = (JavaType.GenericTypeVariable) type;
125+
126+
if (!genericType.getName().equals("?")) {
127+
return new J.Identifier(Tree.randomId(),
128+
space,
129+
Markers.EMPTY,
130+
genericType.getName(),
131+
type,
132+
null
133+
);
134+
}
135+
JLeftPadded<J.Wildcard.Bound> bound = null;
136+
NameTree boundedType = null;
137+
if (genericType.getVariance() == JavaType.GenericTypeVariable.Variance.COVARIANT) {
138+
bound = new JLeftPadded<>(Space.format(" "), J.Wildcard.Bound.Extends, Markers.EMPTY);
139+
} else if (genericType.getVariance() == JavaType.GenericTypeVariable.Variance.CONTRAVARIANT) {
140+
bound = new JLeftPadded<>(Space.format(" "), J.Wildcard.Bound.Super, Markers.EMPTY);
141+
}
142+
143+
if (!genericType.getBounds().isEmpty()) {
144+
boundedType = buildTypeTree(genericType.getBounds().get(0), Space.format(" "));
145+
if (boundedType == null) {
146+
return null;
147+
}
148+
}
149+
150+
return new J.Wildcard(
151+
Tree.randomId(),
152+
space,
153+
Markers.EMPTY,
154+
bound,
155+
boundedType
156+
);
157+
}
158+
return null;
159+
160+
}
161+
}
162+

components/sbm-recipes-boot-upgrade/src/main/resources/recipes/boot-2.7-3.0-dependency-version-update.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@
9595
description: Replace javax with new jakarta packages
9696
openRewriteRecipeName: org.openrewrite.java.migrate.JavaxMigrationToJakarta
9797

98+
- type: org.springframework.sbm.engine.recipe.OpenRewriteNamedRecipeAdapter
99+
condition:
100+
type: org.springframework.sbm.boot.upgrade.common.conditions.HasSpringBootParentOfVersion
101+
versionStartingWith: "3.0."
102+
description: Add CrudRepository interface extension additionaly to PagingAndSortingRepository
103+
openRewriteRecipeName: org.springframework.sbm.boot.upgrade_27_30.CrudRepositoryExtension
104+
98105
- type: org.springframework.sbm.engine.recipe.OpenRewriteDeclarativeRecipeAdapter
99106
condition:
100107
type: org.springframework.sbm.boot.upgrade.common.conditions.HasSpringBootParentOfVersion

0 commit comments

Comments
 (0)