Skip to content

Commit 2713377

Browse files
bottemavfabapp2
authored andcommitted
Migrate JAX-RS default value annotation (#604)
* Add recipe CopyAnnotationAttribute which copies the value of an attribute from a source annotation to a target annotation * Add recipe RemoveAnnotationIfAccompanied which removes an annotation if it is accompanied by another annotation * Combine both recipes CopyAnnotationAttribute and RemoveAnnotationIfAccompanied in the recipe ReplaceRequestParameterProperties * fix BootifyJaxRsAnnotationsRecipeTest * add author tag
1 parent 3cf5bca commit 2713377

File tree

12 files changed

+992
-4
lines changed

12 files changed

+992
-4
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public String getHelloWorldJSON(@PathVariable("name") String name) throws Except
5050
}
5151
5252
@RequestMapping(value = "/json", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
53-
public String getAllPersons(@RequestParam(required = false, value = "q") String searchBy) throws Exception {
53+
public String getAllPersons(@RequestParam(required = false, value = "q") String searchBy, @RequestParam(required = false, defaultValue = "0", value = "page") int page) throws Exception {
5454
return "{\\"message\\":\\"No person here...\\"";
5555
}
5656

applications/spring-shell/src/test/resources/testcode/bootify-jaxrs/src/main/java/com/example/jee/app/PersonController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public String getHelloWorldJSON(@PathParam("name") String name) throws Exception
2020
@Path("/json")
2121
@Produces(MediaType.APPLICATION_JSON)
2222
@Consumes(MediaType.APPLICATION_JSON)
23-
public String getAllPersons(@QueryParam("q") String searchBy) throws Exception {
23+
public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception {
2424
return "{\"message\":\"No person here...\"";
2525
}
2626

components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.sbm.jee.jaxrs;
1717

18-
import lombok.RequiredArgsConstructor;
1918
import org.openrewrite.java.ChangeType;
2019
import org.openrewrite.java.JavaParser;
2120
import org.springframework.context.annotation.Bean;
@@ -35,10 +34,10 @@
3534
import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation;
3635
import org.springframework.sbm.jee.jaxrs.actions.ConvertJaxRsAnnotations;
3736
import org.springframework.sbm.jee.jaxrs.recipes.ReplaceMediaType;
37+
import org.springframework.sbm.jee.jaxrs.recipes.ReplaceRequestParameterProperties;
3838
import org.springframework.sbm.jee.jaxrs.recipes.SwapCacheControl;
3939
import org.springframework.sbm.jee.jaxrs.recipes.SwapHttHeaders;
4040
import org.springframework.sbm.jee.jaxrs.recipes.SwapResponseWithResponseEntity;
41-
import org.springframework.sbm.support.openrewrite.java.AddOrReplaceAnnotationAttribute;
4241

4342
import java.util.List;
4443
import java.util.function.Supplier;
@@ -128,6 +127,12 @@ public Recipe jaxRs(RewriteRecipeLoader rewriteRecipeLoader, RewriteRecipeRunner
128127
.recipe(new SwapResponseWithResponseEntity(javaParserSupplier))
129128
.build(),
130129

130+
JavaRecipeAction.builder()
131+
.condition(HasAnnotation.builder().annotation("org.springframework.web.bind.annotation.RequestParam").build())
132+
.description("Replace the JAX-RS properties of a request parameter (like default value) with it's Spring equivalent.")
133+
.recipe(new ReplaceRequestParameterProperties())
134+
.build(),
135+
131136
OpenRewriteDeclarativeRecipeAdapter.builder()
132137
.condition(HasAnnotation.builder().annotation("org.springframework.web.bind.annotation.RequestParam").build())
133138
.description("Adds required=false to all @RequestParam annotations")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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.jee.jaxrs.recipes;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.Option;
23+
import org.openrewrite.Recipe;
24+
import org.openrewrite.TreeVisitor;
25+
import org.openrewrite.java.JavaIsoVisitor;
26+
import org.openrewrite.java.search.UsesType;
27+
import org.springframework.sbm.jee.jaxrs.recipes.visitors.CopyAnnotationAttributeVisitor;
28+
29+
/**
30+
* @author Vincent Botteman
31+
*/
32+
@Value
33+
@EqualsAndHashCode(callSuper = true)
34+
public class CopyAnnotationAttribute extends Recipe {
35+
@Option(displayName = "Source Annotation Type",
36+
description = "The fully qualified name of the source annotation.",
37+
example = "org.junit.Test")
38+
String sourceAnnotationType;
39+
40+
@Option(displayName = "Source Attribute name",
41+
description = "The name of the attribute on the source annotation containing the value to copy.",
42+
example = "timeout")
43+
String sourceAttributeName;
44+
45+
@Option(displayName = "Target Annotation Type",
46+
description = "The fully qualified name of the target annotation.",
47+
example = "org.junit.Test")
48+
String targetAnnotationType;
49+
50+
@Option(displayName = "Target Attribute name",
51+
description = "The name of the attribute on the target annotation which must be set to the value of the source attribute.",
52+
example = "timeout")
53+
String targetAttributeName;
54+
55+
@Override
56+
protected TreeVisitor<?, ExecutionContext> getSingleSourceApplicableTest() {
57+
return new UsesType<>(sourceAnnotationType);
58+
}
59+
60+
@Override
61+
public @NotNull String getDisplayName() {
62+
return "Copy an annotation attribute to another annotation";
63+
}
64+
65+
@Override
66+
public @NotNull String getDescription() {
67+
return "Copy the value of an annotation attribute to another annotation attribute.";
68+
}
69+
70+
@Override
71+
protected @NotNull JavaIsoVisitor<ExecutionContext> getVisitor() {
72+
return new CopyAnnotationAttributeVisitor(
73+
sourceAnnotationType,
74+
sourceAttributeName,
75+
targetAnnotationType,
76+
targetAttributeName
77+
);
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.jee.jaxrs.recipes;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.Option;
23+
import org.openrewrite.Recipe;
24+
import org.openrewrite.TreeVisitor;
25+
import org.openrewrite.java.search.UsesType;
26+
import org.springframework.sbm.jee.jaxrs.recipes.visitors.RemoveAnnotationIfAccompaniedVisitor;
27+
28+
/**
29+
* @author Vincent Botteman
30+
*/
31+
@EqualsAndHashCode(callSuper = true)
32+
@Value
33+
public class RemoveAnnotationIfAccompanied extends Recipe {
34+
@Option(displayName = "Annotation Type to remove",
35+
description = "The fully qualified name of the annotation to remove.",
36+
example = "org.junit.Test")
37+
String annotationTypeToRemove;
38+
39+
@Option(displayName = "Annotation Type which must also be present",
40+
description = "The fully qualified name of the annotation that must also be present.",
41+
example = "org.junit.Test")
42+
String additionalAnnotationType;
43+
44+
@Override
45+
public @NotNull String getDisplayName() {
46+
return "Remove annotation if accompanied by the other annotation";
47+
}
48+
49+
@Override
50+
public @NotNull String getDescription() {
51+
return "Remove matching annotation if the other annotation is also present.";
52+
}
53+
54+
@Override
55+
protected TreeVisitor<?, ExecutionContext> getSingleSourceApplicableTest() {
56+
return new UsesType<>(annotationTypeToRemove);
57+
}
58+
59+
@Override
60+
public @NotNull RemoveAnnotationIfAccompaniedVisitor getVisitor() {
61+
return new RemoveAnnotationIfAccompaniedVisitor(annotationTypeToRemove, additionalAnnotationType);
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.jee.jaxrs.recipes;
17+
18+
import org.jetbrains.annotations.NotNull;
19+
import org.openrewrite.Recipe;
20+
21+
/**
22+
* @author Vincent Botteman
23+
*/
24+
public class ReplaceRequestParameterProperties extends Recipe {
25+
public ReplaceRequestParameterProperties() {
26+
doNext(new CopyAnnotationAttribute(
27+
"javax.ws.rs.DefaultValue", "value", "org.springframework.web.bind.annotation.RequestParam", "defaultValue")
28+
);
29+
doNext(new RemoveAnnotationIfAccompanied(
30+
"javax.ws.rs.DefaultValue", "org.springframework.web.bind.annotation.RequestParam"
31+
));
32+
}
33+
34+
@Override
35+
public @NotNull String getDisplayName() {
36+
return "Migrate the properties of a request parameter: default value, ...";
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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.jee.jaxrs.recipes.visitors;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.openrewrite.Cursor;
22+
import org.openrewrite.ExecutionContext;
23+
import org.openrewrite.java.AddOrUpdateAnnotationAttribute;
24+
import org.openrewrite.java.JavaIsoVisitor;
25+
import org.openrewrite.java.tree.J;
26+
import org.openrewrite.java.tree.JavaType;
27+
import org.openrewrite.java.tree.TypeUtils;
28+
import org.springframework.sbm.jee.utils.AnnotationUtils;
29+
30+
import java.util.Optional;
31+
32+
/**
33+
* @author Vincent Botteman
34+
*/
35+
@Value
36+
@EqualsAndHashCode(callSuper = true)
37+
public class CopyAnnotationAttributeVisitor extends JavaIsoVisitor<ExecutionContext> {
38+
String sourceAnnotationType;
39+
String sourceAttributeName;
40+
String targetAnnotationType;
41+
String targetAttributeName;
42+
43+
@Override
44+
public J.Annotation visitAnnotation(@NotNull J.Annotation annotation, @NotNull ExecutionContext ctx) {
45+
J.Annotation a = super.visitAnnotation(annotation, ctx);
46+
47+
if (!TypeUtils.isOfClassType(a.getType(), targetAnnotationType)) {
48+
return a;
49+
}
50+
51+
Cursor parent = getCursor().getParent();
52+
if (parent == null) {
53+
return a;
54+
}
55+
J.VariableDeclarations variableDeclaration = parent.getValue();
56+
Optional<J.Literal> optionalSourceAnnotationAttributeValue = getSourceAnnotationAttributeValue(variableDeclaration);
57+
if (optionalSourceAnnotationAttributeValue.isEmpty()) {
58+
return a;
59+
}
60+
61+
J.Literal sourceAnnotationAttributeValue = optionalSourceAnnotationAttributeValue.get();
62+
if (sourceAnnotationAttributeValue.getValue() != null) {
63+
// If the annotation type is a shallow class then JavaType.getMethods is empty and AddOrUpdateAnnotationAttribute can't determine if the datatype of the attribute is String or not
64+
String targetAttributeValue = annotation.getType() instanceof JavaType.ShallowClass ? sourceAnnotationAttributeValue.getValueSource() : sourceAnnotationAttributeValue.getValue().toString();
65+
JavaIsoVisitor<ExecutionContext> addOrUpdateAnnotationAttributeVisitor = new AddOrUpdateAnnotationAttribute(targetAnnotationType, targetAttributeName, targetAttributeValue, false)
66+
.getVisitor();
67+
if (targetAnnotationOnlyHasOneLiteralArgument(a)) {
68+
a = (J.Annotation) addOrUpdateAnnotationAttributeVisitor.visit(a, ctx, getCursor());
69+
}
70+
return (J.Annotation) addOrUpdateAnnotationAttributeVisitor.visit(a, ctx, getCursor());
71+
}
72+
return a;
73+
}
74+
75+
private Optional<J.Literal> getSourceAnnotationAttributeValue(J.VariableDeclarations methodParameterDeclaration) {
76+
return methodParameterDeclaration.getLeadingAnnotations().stream()
77+
.filter(annotation -> TypeUtils.isOfClassType(annotation.getType(), sourceAnnotationType))
78+
.flatMap(annotation -> AnnotationUtils.getAttributeValue(annotation, sourceAttributeName).stream())
79+
.findAny();
80+
}
81+
82+
private boolean targetAnnotationOnlyHasOneLiteralArgument(@NotNull J.Annotation annotation) {
83+
return annotation.getArguments() != null && annotation.getArguments().size() == 1 && annotation.getArguments().get(0) instanceof J.Literal;
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.jee.jaxrs.recipes.visitors;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.java.JavaIsoVisitor;
23+
import org.openrewrite.java.RemoveAnnotation;
24+
import org.openrewrite.java.tree.J;
25+
import org.openrewrite.java.tree.JavaType;
26+
import org.openrewrite.java.tree.TypeUtils;
27+
28+
/**
29+
* @author Vincent Botteman
30+
*/
31+
@Value
32+
@EqualsAndHashCode(callSuper = true)
33+
public class RemoveAnnotationIfAccompaniedVisitor extends JavaIsoVisitor<ExecutionContext> {
34+
private static final String ANNOTATION_REMOVED_KEY = "annotationRemoved";
35+
String annotationTypeToRemove;
36+
String additionalAnnotationType;
37+
38+
@Override
39+
public J.VariableDeclarations visitVariableDeclarations(@NotNull J.VariableDeclarations multiVariable, @NotNull ExecutionContext ctx) {
40+
J.VariableDeclarations m = super.visitVariableDeclarations(multiVariable, ctx);
41+
42+
if (variableDeclarationContainsAnnotationType(m, annotationTypeToRemove) && variableDeclarationContainsAnnotationType(m, additionalAnnotationType)) {
43+
JavaIsoVisitor<ExecutionContext> removeAnnotationVisitor = new RemoveAnnotation("@" + annotationTypeToRemove)
44+
.getVisitor();
45+
m = (J.VariableDeclarations) removeAnnotationVisitor.visit(m, ctx, getCursor());
46+
this.maybeRemoveImport(TypeUtils.asFullyQualified(JavaType.buildType(annotationTypeToRemove)));
47+
}
48+
49+
return m;
50+
}
51+
52+
private boolean variableDeclarationContainsAnnotationType(J.VariableDeclarations variableDeclaration, String annotationType) {
53+
return variableDeclaration.getLeadingAnnotations().stream().anyMatch(annotation -> TypeUtils.isOfClassType(annotation.getType(), annotationType));
54+
}
55+
}

0 commit comments

Comments
 (0)