diff --git a/applications/spring-shell/src/test/java/org/springframework/sbm/recipes/MigrateJaxRsAnnotationsRecipeIntegrationTest.java b/applications/spring-shell/src/test/java/org/springframework/sbm/recipes/MigrateJaxRsAnnotationsRecipeIntegrationTest.java index e253b232e..1443d53e1 100644 --- a/applications/spring-shell/src/test/java/org/springframework/sbm/recipes/MigrateJaxRsAnnotationsRecipeIntegrationTest.java +++ b/applications/spring-shell/src/test/java/org/springframework/sbm/recipes/MigrateJaxRsAnnotationsRecipeIntegrationTest.java @@ -50,7 +50,7 @@ public String getHelloWorldJSON(@PathVariable("name") String name) throws Except } @RequestMapping(value = "/json", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) - public String getAllPersons(@RequestParam(required = false, value = "q") String searchBy) throws Exception { + public String getAllPersons(@RequestParam(required = false, value = "q") String searchBy, @RequestParam(required = false, defaultValue = "0", value = "page") int page) throws Exception { return "{\\"message\\":\\"No person here...\\""; } diff --git a/applications/spring-shell/src/test/resources/testcode/bootify-jaxrs/src/main/java/com/example/jee/app/PersonController.java b/applications/spring-shell/src/test/resources/testcode/bootify-jaxrs/src/main/java/com/example/jee/app/PersonController.java index 517e6d630..d355f7913 100644 --- a/applications/spring-shell/src/test/resources/testcode/bootify-jaxrs/src/main/java/com/example/jee/app/PersonController.java +++ b/applications/spring-shell/src/test/resources/testcode/bootify-jaxrs/src/main/java/com/example/jee/app/PersonController.java @@ -20,7 +20,7 @@ public String getHelloWorldJSON(@PathParam("name") String name) throws Exception @Path("/json") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - public String getAllPersons(@QueryParam("q") String searchBy) throws Exception { + public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { return "{\"message\":\"No person here...\""; } diff --git a/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java index dba0edda7..98053fdd5 100644 --- a/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java +++ b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java @@ -15,7 +15,6 @@ */ package org.springframework.sbm.jee.jaxrs; -import lombok.RequiredArgsConstructor; import org.openrewrite.java.ChangeType; import org.openrewrite.java.JavaParser; import org.springframework.context.annotation.Bean; @@ -35,10 +34,10 @@ import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation; import org.springframework.sbm.jee.jaxrs.actions.ConvertJaxRsAnnotations; import org.springframework.sbm.jee.jaxrs.recipes.ReplaceMediaType; +import org.springframework.sbm.jee.jaxrs.recipes.ReplaceRequestParameterProperties; import org.springframework.sbm.jee.jaxrs.recipes.SwapCacheControl; import org.springframework.sbm.jee.jaxrs.recipes.SwapHttHeaders; import org.springframework.sbm.jee.jaxrs.recipes.SwapResponseWithResponseEntity; -import org.springframework.sbm.support.openrewrite.java.AddOrReplaceAnnotationAttribute; import java.util.List; import java.util.function.Supplier; @@ -128,6 +127,12 @@ public Recipe jaxRs(RewriteRecipeLoader rewriteRecipeLoader, RewriteRecipeRunner .recipe(new SwapResponseWithResponseEntity(javaParserSupplier)) .build(), + JavaRecipeAction.builder() + .condition(HasAnnotation.builder().annotation("org.springframework.web.bind.annotation.RequestParam").build()) + .description("Replace the JAX-RS properties of a request parameter (like default value) with it's Spring equivalent.") + .recipe(new ReplaceRequestParameterProperties()) + .build(), + OpenRewriteDeclarativeRecipeAdapter.builder() .condition(HasAnnotation.builder().annotation("org.springframework.web.bind.annotation.RequestParam").build()) .description("Adds required=false to all @RequestParam annotations") diff --git a/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java new file mode 100644 index 000000000..b00759482 --- /dev/null +++ b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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.springframework.sbm.jee.jaxrs.recipes; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.search.UsesType; +import org.springframework.sbm.jee.jaxrs.recipes.visitors.CopyAnnotationAttributeVisitor; + +/** + * @author Vincent Botteman + */ +@Value +@EqualsAndHashCode(callSuper = true) +public class CopyAnnotationAttribute extends Recipe { + @Option(displayName = "Source Annotation Type", + description = "The fully qualified name of the source annotation.", + example = "org.junit.Test") + String sourceAnnotationType; + + @Option(displayName = "Source Attribute name", + description = "The name of the attribute on the source annotation containing the value to copy.", + example = "timeout") + String sourceAttributeName; + + @Option(displayName = "Target Annotation Type", + description = "The fully qualified name of the target annotation.", + example = "org.junit.Test") + String targetAnnotationType; + + @Option(displayName = "Target Attribute name", + description = "The name of the attribute on the target annotation which must be set to the value of the source attribute.", + example = "timeout") + String targetAttributeName; + + @Override + protected TreeVisitor getSingleSourceApplicableTest() { + return new UsesType<>(sourceAnnotationType); + } + + @Override + public @NotNull String getDisplayName() { + return "Copy an annotation attribute to another annotation"; + } + + @Override + public @NotNull String getDescription() { + return "Copy the value of an annotation attribute to another annotation attribute."; + } + + @Override + protected @NotNull JavaIsoVisitor getVisitor() { + return new CopyAnnotationAttributeVisitor( + sourceAnnotationType, + sourceAttributeName, + targetAnnotationType, + targetAttributeName + ); + } +} diff --git a/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java new file mode 100644 index 000000000..e9e272507 --- /dev/null +++ b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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.springframework.sbm.jee.jaxrs.recipes; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.search.UsesType; +import org.springframework.sbm.jee.jaxrs.recipes.visitors.RemoveAnnotationIfAccompaniedVisitor; + +/** + * @author Vincent Botteman + */ +@EqualsAndHashCode(callSuper = true) +@Value +public class RemoveAnnotationIfAccompanied extends Recipe { + @Option(displayName = "Annotation Type to remove", + description = "The fully qualified name of the annotation to remove.", + example = "org.junit.Test") + String annotationTypeToRemove; + + @Option(displayName = "Annotation Type which must also be present", + description = "The fully qualified name of the annotation that must also be present.", + example = "org.junit.Test") + String additionalAnnotationType; + + @Override + public @NotNull String getDisplayName() { + return "Remove annotation if accompanied by the other annotation"; + } + + @Override + public @NotNull String getDescription() { + return "Remove matching annotation if the other annotation is also present."; + } + + @Override + protected TreeVisitor getSingleSourceApplicableTest() { + return new UsesType<>(annotationTypeToRemove); + } + + @Override + public @NotNull RemoveAnnotationIfAccompaniedVisitor getVisitor() { + return new RemoveAnnotationIfAccompaniedVisitor(annotationTypeToRemove, additionalAnnotationType); + } +} \ No newline at end of file diff --git a/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java new file mode 100644 index 000000000..5c9bf7a47 --- /dev/null +++ b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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.springframework.sbm.jee.jaxrs.recipes; + +import org.jetbrains.annotations.NotNull; +import org.openrewrite.Recipe; + +/** + * @author Vincent Botteman + */ +public class ReplaceRequestParameterProperties extends Recipe { + public ReplaceRequestParameterProperties() { + doNext(new CopyAnnotationAttribute( + "javax.ws.rs.DefaultValue", "value", "org.springframework.web.bind.annotation.RequestParam", "defaultValue") + ); + doNext(new RemoveAnnotationIfAccompanied( + "javax.ws.rs.DefaultValue", "org.springframework.web.bind.annotation.RequestParam" + )); + } + + @Override + public @NotNull String getDisplayName() { + return "Migrate the properties of a request parameter: default value, ..."; + } +} \ No newline at end of file diff --git a/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java new file mode 100644 index 000000000..2e4196edb --- /dev/null +++ b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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.springframework.sbm.jee.jaxrs.recipes.visitors; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.AddOrUpdateAnnotationAttribute; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; +import org.springframework.sbm.jee.utils.AnnotationUtils; + +import java.util.Optional; + +/** + * @author Vincent Botteman + */ +@Value +@EqualsAndHashCode(callSuper = true) +public class CopyAnnotationAttributeVisitor extends JavaIsoVisitor { + String sourceAnnotationType; + String sourceAttributeName; + String targetAnnotationType; + String targetAttributeName; + + @Override + public J.Annotation visitAnnotation(@NotNull J.Annotation annotation, @NotNull ExecutionContext ctx) { + J.Annotation a = super.visitAnnotation(annotation, ctx); + + if (!TypeUtils.isOfClassType(a.getType(), targetAnnotationType)) { + return a; + } + + Cursor parent = getCursor().getParent(); + if (parent == null) { + return a; + } + J.VariableDeclarations variableDeclaration = parent.getValue(); + Optional optionalSourceAnnotationAttributeValue = getSourceAnnotationAttributeValue(variableDeclaration); + if (optionalSourceAnnotationAttributeValue.isEmpty()) { + return a; + } + + J.Literal sourceAnnotationAttributeValue = optionalSourceAnnotationAttributeValue.get(); + if (sourceAnnotationAttributeValue.getValue() != null) { + // 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 + String targetAttributeValue = annotation.getType() instanceof JavaType.ShallowClass ? sourceAnnotationAttributeValue.getValueSource() : sourceAnnotationAttributeValue.getValue().toString(); + JavaIsoVisitor addOrUpdateAnnotationAttributeVisitor = new AddOrUpdateAnnotationAttribute(targetAnnotationType, targetAttributeName, targetAttributeValue, false) + .getVisitor(); + if (targetAnnotationOnlyHasOneLiteralArgument(a)) { + a = (J.Annotation) addOrUpdateAnnotationAttributeVisitor.visit(a, ctx, getCursor()); + } + return (J.Annotation) addOrUpdateAnnotationAttributeVisitor.visit(a, ctx, getCursor()); + } + return a; + } + + private Optional getSourceAnnotationAttributeValue(J.VariableDeclarations methodParameterDeclaration) { + return methodParameterDeclaration.getLeadingAnnotations().stream() + .filter(annotation -> TypeUtils.isOfClassType(annotation.getType(), sourceAnnotationType)) + .flatMap(annotation -> AnnotationUtils.getAttributeValue(annotation, sourceAttributeName).stream()) + .findAny(); + } + + private boolean targetAnnotationOnlyHasOneLiteralArgument(@NotNull J.Annotation annotation) { + return annotation.getArguments() != null && annotation.getArguments().size() == 1 && annotation.getArguments().get(0) instanceof J.Literal; + } +} \ No newline at end of file diff --git a/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/RemoveAnnotationIfAccompaniedVisitor.java b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/RemoveAnnotationIfAccompaniedVisitor.java new file mode 100644 index 000000000..5bdde2633 --- /dev/null +++ b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/RemoveAnnotationIfAccompaniedVisitor.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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.springframework.sbm.jee.jaxrs.recipes.visitors; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.RemoveAnnotation; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; + +/** + * @author Vincent Botteman + */ +@Value +@EqualsAndHashCode(callSuper = true) +public class RemoveAnnotationIfAccompaniedVisitor extends JavaIsoVisitor { + private static final String ANNOTATION_REMOVED_KEY = "annotationRemoved"; + String annotationTypeToRemove; + String additionalAnnotationType; + + @Override + public J.VariableDeclarations visitVariableDeclarations(@NotNull J.VariableDeclarations multiVariable, @NotNull ExecutionContext ctx) { + J.VariableDeclarations m = super.visitVariableDeclarations(multiVariable, ctx); + + if (variableDeclarationContainsAnnotationType(m, annotationTypeToRemove) && variableDeclarationContainsAnnotationType(m, additionalAnnotationType)) { + JavaIsoVisitor removeAnnotationVisitor = new RemoveAnnotation("@" + annotationTypeToRemove) + .getVisitor(); + m = (J.VariableDeclarations) removeAnnotationVisitor.visit(m, ctx, getCursor()); + this.maybeRemoveImport(TypeUtils.asFullyQualified(JavaType.buildType(annotationTypeToRemove))); + } + + return m; + } + + private boolean variableDeclarationContainsAnnotationType(J.VariableDeclarations variableDeclaration, String annotationType) { + return variableDeclaration.getLeadingAnnotations().stream().anyMatch(annotation -> TypeUtils.isOfClassType(annotation.getType(), annotationType)); + } +} diff --git a/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/utils/AnnotationUtils.java b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/utils/AnnotationUtils.java new file mode 100644 index 000000000..892b14f80 --- /dev/null +++ b/components/sbm-recipes-jee-to-boot/src/main/java/org/springframework/sbm/jee/utils/AnnotationUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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.springframework.sbm.jee.utils; + +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.springframework.util.CollectionUtils; + +import java.util.Optional; + +/** + * @author Vincent Botteman + */ +public final class AnnotationUtils { + public static final String VALUE_ATTRIBUTE_NAME = "value"; + + private AnnotationUtils() { + } + + public static Optional getAttributeValue(J.Annotation annotation, String attributeName) { + if (CollectionUtils.isEmpty(annotation.getArguments())) { + return Optional.empty(); + } + + for (Expression argument : annotation.getArguments()) { + if (argument instanceof J.Assignment as) { + J.Identifier variable = (J.Identifier) as.getVariable(); + if (variable.getSimpleName().equals(attributeName)) { + return Optional.of((J.Literal) as.getAssignment()); + } + } else if (argument instanceof J.Literal literal && VALUE_ATTRIBUTE_NAME.equals(attributeName)) { + return Optional.of(literal); + } + } + + return Optional.empty(); + } +} diff --git a/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java b/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java index 7facd58ef..6acb4a707 100644 --- a/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java +++ b/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java @@ -48,6 +48,7 @@ void test() { JavaRecipeAction.class, JavaRecipeAction.class, JavaRecipeAction.class, + JavaRecipeAction.class, OpenRewriteDeclarativeRecipeAdapter.class); } } diff --git a/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java b/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java new file mode 100644 index 000000000..a75772de5 --- /dev/null +++ b/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java @@ -0,0 +1,410 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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.springframework.sbm.jee.jaxrs.recipes; + +import org.junit.jupiter.api.Test; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.project.resource.TestProjectContext; + +import static org.assertj.core.api.Assertions.assertThat; + +class CopyAnnotationAttributeTest { + private final static String SPRING_VERSION = "5.3.13"; + + @Test + void givenBothAnnotationsArePresent_thenTheAttributeIsCopied() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(defaultValue = "default-value", value = "q") String searchString) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, expected); + } + + @Test + void givenBothAnnotationsArePresentOnMethodParameterWithTypeInt_thenTheAttributeIsCopied() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("0") @RequestParam(value = "page") int page) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("0") @RequestParam(defaultValue = "0", value = "page") int page) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, expected); + } + + @Test + void givenTheTargetAnnotationIsPositionedBeforeTheSourceAnnotation_thenTheAttributeIsCopied() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@RequestParam(value = "q") @DefaultValue("default-value") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@RequestParam(defaultValue = "default-value", value = "q") @DefaultValue("default-value") String searchString) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, expected); + } + + @Test + void givenTheTargetAnnotationOnlyHasALiteralValueAndTheTargetAttributeIsNotValue_thenTheTargetAnnotationIsExpandedAndTheAttributeIsCopied() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam("q") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(defaultValue = "default-value", value = "q") String searchString) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, expected); + } + + @Test + void givenTheMethodHasMultipleParameters_thenOnlyTheMethodParametersAreModifiedWhichContainBothAnnotations() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import org.springframework.web.bind.annotation.RequestHeader; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test( + @DefaultValue("default-value-1") @RequestParam(value = "p1") String parameter1, + @RequestParam(value = "p2") String parameter2, + String parameter3, + @DefaultValue("default-value-4") @RequestHeader(value = "myOwnHeader") String myHeader, + @DefaultValue(value = "default-value-5") @RequestParam("p5") String parameter5 + ) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import org.springframework.web.bind.annotation.RequestHeader; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test( + @DefaultValue("default-value-1") @RequestParam(defaultValue = "default-value-1", value = "p1") String parameter1, + @RequestParam(value = "p2") String parameter2, + String parameter3, + @DefaultValue("default-value-4") @RequestHeader(value = "myOwnHeader") String myHeader, + @DefaultValue(value = "default-value-5") @RequestParam(defaultValue = "default-value-5", value = "p5") String parameter5 + ) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, expected); + } + + @Test + void givenThereAreOtherAnnotationsPresentThanTheSourceAndTargetAnnotation_thenTheAttributeIsCopied() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + import javax.validation.constraints.NotNull; + + class ControllerClass { + public String test(@RequestParam(value = "q") @NotNull @DefaultValue("default-value") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + import javax.validation.constraints.NotNull; + + class ControllerClass { + public String test(@RequestParam(defaultValue = "default-value", value = "q") @NotNull @DefaultValue("default-value") String searchString) { + return "Hello"; + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies( + "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", + "jakarta.validation:jakarta.validation-api:2.0.2", + "org.springframework:spring-web:" + SPRING_VERSION + ) + .build(); + + CopyAnnotationAttribute sut = new CopyAnnotationAttribute( + "javax.ws.rs.DefaultValue", "value", "org.springframework.web.bind.annotation.RequestParam", "defaultValue"); + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + javaSource.apply(sut); + + assertThat(javaSource.print()).isEqualTo(expected); + } + + @Test + void givenTheTargetAnnotationRelatesToAnotherMethodParameterThanTheSourceAnnotation_thenNothingChanges() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test( + @RequestParam(value = "q") String parameter1, + @DefaultValue("default-value") String parameter2 + ) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, sourceCode); + } + + @Test + void givenOnlyTheTargetAnnotationIsPresent_thenNothingChanges() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, sourceCode); + } + + @Test + void givenNoMethodParametersArePresent_thenNothingChanges() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test() { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, sourceCode); + } + + @Test + void givenTheTargetAnnotationHasNoAttributesAndTheTargetAttributeIsNotValue_thenTheAttributeIsCopiedAsAssignment() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(defaultValue = "default-value") String searchString) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, expected); + } + + @Test + void givenTheTargetAnnotationAlreadyHasAnAttributeWithTheTargetValue_thenNothingChanges() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(defaultValue = "default-value") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(defaultValue = "default-value") String searchString) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, expected); + } + + @Test + void givenTheTargetAnnotationAlreadyHasAnAttributeWithAnotherValue_thenTheValueOfTheTargetAttributeIsOverwritten() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(defaultValue = "original-default-value") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(defaultValue = "default-value") String searchString) { + return "Hello"; + } + } + """; + + testCopyAnnotationAttribute(sourceCode, expected); + } + + @Test + void givenTheTargetAnnotationHasNoAttributesAndTheTargetAttributeIsValue_thenTheAttributeIsCopiedAsLiteralValue() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam("default-value") String searchString) { + return "Hello"; + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies( + "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", + "org.springframework:spring-web:" + SPRING_VERSION + ) + .build(); + + CopyAnnotationAttribute sut = new CopyAnnotationAttribute( + "javax.ws.rs.DefaultValue", "value", "org.springframework.web.bind.annotation.RequestParam", "value"); + + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + javaSource.apply(sut); + + assertThat(javaSource.print()).isEqualTo(expected); + } + + private static void testCopyAnnotationAttribute(String sourceCode, String expected) { + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies( + "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", + "org.springframework:spring-web:" + SPRING_VERSION + ) + .build(); + + CopyAnnotationAttribute sut = new CopyAnnotationAttribute( + "javax.ws.rs.DefaultValue", "value", "org.springframework.web.bind.annotation.RequestParam", "defaultValue"); + + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + javaSource.apply(sut); + + assertThat(javaSource.print()).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java b/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java new file mode 100644 index 000000000..c18916b78 --- /dev/null +++ b/components/sbm-recipes-jee-to-boot/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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.springframework.sbm.jee.jaxrs.recipes; + +import org.junit.jupiter.api.Test; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.project.resource.TestProjectContext; + +import static org.assertj.core.api.Assertions.assertThat; + +class RemoveAnnotationIfAccompaniedTest { + private final static String SPRING_VERSION = "5.3.13"; + + @Test + void givenBothAnnotationsArePresentOnTheFirstMethodParameter_thenTheAnnotationIsRemoved() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + + class ControllerClass { + public String test(@RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + testRemoveAnnotation(sourceCode, expected); + } + + @Test + void givenBothAnnotationsArePresentOnTheFirstMethodParameterInReverseOrder_thenTheAnnotationIsRemoved() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@RequestParam(value = "q") @DefaultValue("default-value") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + + class ControllerClass { + public String test(@RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + testRemoveAnnotation(sourceCode, expected); + } + + @Test + void givenBothAnnotationsArePresentOnTheFirstMethodParameterAndPrecededByAnotherOne_thenTheAnnotationIsRemoved() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.validation.constraints.NotNull; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@NotNull @DefaultValue("default-value") @RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.validation.constraints.NotNull; + + class ControllerClass { + public String test(@NotNull @RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + testRemoveAnnotation(sourceCode, expected); + } + + @Test + void givenBothAnnotationsArePresentOnTheSecondMethodParameter_thenTheAnnotationIsRemoved() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@RequestParam String name, @DefaultValue("default-value") @RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + String expected = """ + import org.springframework.web.bind.annotation.RequestParam; + + class ControllerClass { + public String test(@RequestParam String name, @RequestParam(value = "q") String searchString) { + return "Hello"; + } + } + """; + + testRemoveAnnotation(sourceCode, expected); + } + + @Test + void givenBothAnnotationsArePresentButOnDifferentMethodParameters_thenNothingChanges() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestHeader; + import org.springframework.web.bind.annotation.RequestParam; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@RequestParam String name, @DefaultValue("default-value") @RequestHeader String myHeader) { + return "Hello"; + } + } + """; + + testRemoveAnnotation(sourceCode, sourceCode); + } + + @Test + void givenOnlyAnnotationToRemoveIsPresent_thenNothingChanges() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestHeader; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test(@DefaultValue("default-value") @RequestHeader String myHeader) { + return "Hello"; + } + } + """; + + testRemoveAnnotation(sourceCode, sourceCode); + } + + @Test + void givenMethodWithoutParameters_thenNothingChanges() { + String sourceCode = """ + import org.springframework.web.bind.annotation.RequestHeader; + import javax.ws.rs.DefaultValue; + + class ControllerClass { + public String test() { + return "Hello"; + } + } + """; + + testRemoveAnnotation(sourceCode, sourceCode); + } + + private void testRemoveAnnotation(String sourceCode, String expected) { + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies( + "jakarta.ws.rs:jakarta.ws.rs-api:2.1.6", + "jakarta.validation:jakarta.validation-api:2.0.2", + "org.springframework:spring-web:" + SPRING_VERSION + ) + .build(); + + RemoveAnnotationIfAccompanied sut = new RemoveAnnotationIfAccompanied( + "javax.ws.rs.DefaultValue", "org.springframework.web.bind.annotation.RequestParam"); + + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + javaSource.apply(sut); + + assertThat(javaSource.print()).isEqualTo(expected); + } +} \ No newline at end of file