From 6fdf41f401fee196cd85798e4f2a9f32d2f25d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 29 Jul 2024 17:10:34 +0200 Subject: [PATCH 01/19] Organized imports --- .../springframework/sbm/engine/recipe/RewriteRecipeLoader.java | 2 -- .../org/springframework/sbm/java/impl/ClasspathRegistry.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/RewriteRecipeLoader.java b/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/RewriteRecipeLoader.java index 0aa597831..c8b6d1a08 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/RewriteRecipeLoader.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/RewriteRecipeLoader.java @@ -16,7 +16,6 @@ package org.springframework.sbm.engine.recipe; import org.jetbrains.annotations.NotNull; -import org.openrewrite.Contributor; import org.openrewrite.config.DeclarativeRecipe; import org.openrewrite.config.Environment; import org.openrewrite.config.YamlResourceLoader; @@ -30,7 +29,6 @@ import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; // @Component diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/ClasspathRegistry.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/ClasspathRegistry.java index 9d46521f1..2eb20e160 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/ClasspathRegistry.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/ClasspathRegistry.java @@ -18,10 +18,8 @@ import lombok.extern.slf4j.Slf4j; import org.openrewrite.maven.cache.LocalMavenArtifactCache; import org.openrewrite.maven.tree.ResolvedDependency; -import org.openrewrite.maven.tree.Scope; import org.springframework.rewrite.parser.maven.RewriteMavenArtifactDownloader; import org.springframework.sbm.build.api.BuildFile; -import org.springframework.sbm.build.impl.OpenRewriteMavenBuildFile; import org.springframework.sbm.project.parser.DependencyHelper; import java.nio.file.Path; From 70adc030af000e8559a98d7dea10e434615a3846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Tue, 30 Jul 2024 11:02:44 +0200 Subject: [PATCH 02/19] wip: Extract Jax-RS recipes to run in OR --- components/jaxrs-recipes/pom.xml | 245 +++++ .../java/example/recipe/SbmAdapterRecipe.java | 123 +++ .../conditions/HasTypeAnnotation.java | 44 + .../recipes/RewriteMethodInvocation.java | 86 ++ .../ReplaceConstantWithAnotherConstant.java | 129 +++ .../ejb/actions/MigrateEjbAnnotations.java | 138 +++ .../MigrateEjbDeploymentDescriptor.java | 188 ++++ .../jee/ejb/actions/MigrateJndiLookup.java | 191 ++++ .../MigrateLocalStatelessSessionBeans.java | 84 ++ ...grateLocalStatelessSessionBeansHelper.java | 114 +++ .../SingletonAnnotationTemplateMapper.java | 54 + .../StatelessAnnotationTemplateMapper.java | 59 ++ .../jee/ejb/actions/TypeAndSourceFile.java | 30 + ...lAnnotationPresentOnTypeAnnotatedWith.java | 44 + ...grateEjbJarDeploymentDescriptorRecipe.java | 44 + .../sbm/jee/jaxrs/MigrateJaxRsRecipe.java | 159 +++ .../actions/ConvertJaxRsAnnotations.java | 189 ++++ .../recipes/CopyAnnotationAttribute.java | 79 ++ .../RemoveAnnotationIfAccompanied.java | 63 ++ .../jee/jaxrs/recipes/ReplaceMediaType.java | 176 ++++ .../ReplaceRequestParameterProperties.java | 38 + .../recipes/ReplaceResponseEntityBuilder.java | 248 +++++ .../jee/jaxrs/recipes/SwapCacheControl.java | 78 ++ .../jaxrs/recipes/SwapFamilyForSeries.java | 61 ++ .../sbm/jee/jaxrs/recipes/SwapHttHeaders.java | 141 +++ .../SwapResponseWithResponseEntity.java | 376 +++++++ .../recipes/SwapStatusForHttpStatus.java | 117 +++ .../CopyAnnotationAttributeVisitor.java | 85 ++ .../RemoveAnnotationIfAccompaniedVisitor.java | 55 + .../sbm/jee/jaxws/GenerateWebServices.java | 246 +++++ .../sbm/jee/jaxws/MigrateJaxWsRecipe.java | 55 + .../sbm/jee/jaxws/WebServiceDescriptor.java | 295 ++++++ .../sbm/jee/jaxws/WebServiceOperation.java | 90 ++ .../sbm/jee/jaxws/XmlUtils.java | 61 ++ .../jee/jms/actions/AddJmsConfigAction.java | 107 ++ .../ReplaceMdbAnnotationWithJmsListener.java | 97 ++ .../jsf/actions/AddJoinfacesDependencies.java | 147 +++ ...teJsf2ToSpringBootApplicableCondition.java | 41 + ...rateJeeTransactionsToSpringBootAction.java | 129 +++ .../sbm/jee/utils/AnnotationUtils.java | 51 + ...rvletDefinitionFromWebXmlToAnnotation.java | 32 + .../ShouldAddServletComponentScan.java | 76 ++ .../recipes/documentation-actions.yaml | 9 + .../recipes/mark-and-clean-remote-ejbs.yaml | 22 + .../recipes/migrate-annotated-servlets.yaml | 42 + .../main/resources/recipes/migrate-jms.yaml | 52 + .../recipes/migrate-jndi-lookup.yaml | 16 + .../recipes/migrate-jpa-to-spring-boot.yaml | 96 ++ .../migrate-jsf-2.x-to-spring-boot.yaml | 99 ++ .../recipes/migrate-stateless-ejb.yaml | 93 ++ .../recipes/migrate-tx-to-spring-boot.yaml | 18 + .../eclipselink-configuration-class.ftl | 51 + .../resources/templates/jaxws-endpoint.ftl | 26 + .../resources/templates/jaxws-web-config.ftl | 38 + .../main/resources/templates/jms-config.ftl | 41 + .../sbm/AddNewDependencyTest.java | 89 ++ ...edInstantiationOfExecutionContextTest.java | 54 + .../actions/MigrateEjbAnnotationsTest.java | 354 +++++++ .../MigrateEjbDeploymentDescriptorTest.java | 243 +++++ .../ejb/actions/MigrateJndiLookupTest.java | 264 +++++ ...MigrateLocalStatelessSessionBeansTest.java | 171 ++++ ...StatelessAnnotationTemplateMapperTest.java | 147 +++ .../EjbJarDeploymentDescriptorTest.java | 46 + .../MigrateStatelessEjbRecipeTest.java | 104 ++ .../actions/ConvertJaxRsAnnotationsTest.java | 344 +++++++ .../BootifyJaxRsAnnotationsRecipeTest.java | 52 + .../jee/jaxrs/recipes/CacheControlTest.java | 81 ++ .../recipes/ConvertJaxRsAnnotationsTest.java | 239 +++++ .../recipes/CopyAnnotationAttributeTest.java | 410 ++++++++ .../RemoveAnnotationIfAccompaniedTest.java | 201 ++++ .../jaxrs/recipes/ReplaceMediaTypeTest.java | 571 +++++++++++ .../jaxrs/recipes/ResponseBuilderTest.java | 390 +++++++ .../ResponseEntityReplacementTest.java | 955 ++++++++++++++++++ .../recipes/ResponseStatusFamilyTest.java | 175 ++++ .../jee/jaxrs/recipes/ResponseStatusTest.java | 422 ++++++++ .../jaxrs/recipes/SwapHttpHeadersTest.java | 140 +++ .../jee/jaxws/GenerateWebServicesTest.java | 853 ++++++++++++++++ .../jee/jaxws/WebServiceDescriptorTest.java | 148 +++ .../jee/jaxws/WebServiceOperationTest.java | 89 ++ .../sbm/jee/jms/AddJmsConfigTest.java | 193 ++++ ...placeMdbAnnotationWithJmsListenerTest.java | 68 ++ ...AddJoinfacesDependencies_Mojarra_Test.java | 83 ++ ...AddJoinfacesDependencies_MyFaces_Test.java | 95 ++ .../MigrateJsf2xToSpringBootRecipeTest.java | 61 ++ ...JeeTransactionsToSpringBootActionTest.java | 115 +++ .../MigrateTxToSpringBootRecipeTest.java | 52 + .../ShouldAddServletComponentScanTest.java | 92 ++ .../MigrateAnnotatedServletRecipeTest.java | 60 ++ .../jee/jpa/eclipselink-jpa/expected/pom.xml | 20 + .../src/main/java/com/example/SomeClass.java | 3 + .../main/resources/META-INF/persistence.xml | 122 +++ .../jee/jpa/eclipselink-jpa/given/pom.xml | 20 + .../src/main/java/com/example/SomeClass.java | 3 + .../main/resources/META-INF/persistence.xml | 122 +++ .../testcode/jee/web/given/pom.xml | 38 + .../java/com/examples/jee/web/TheServlet.java | 15 + .../web/given/src/main/webapp/WEB-INF/web.xml | 32 + .../testcode/wsdl/calculator.wsdl | 98 ++ 98 files changed, 13302 insertions(+) create mode 100644 components/jaxrs-recipes/pom.xml create mode 100644 components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbAnnotations.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptor.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeans.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansHelper.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/SingletonAnnotationTemplateMapper.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/StatelessAnnotationTemplateMapper.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/TypeAndSourceFile.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/conditions/NoTransactionalAnnotationPresentOnTypeAnnotatedWith.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/recipes/MigrateEjbJarDeploymentDescriptorRecipe.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/RemoveAnnotationIfAccompaniedVisitor.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/GenerateWebServices.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/MigrateJaxWsRecipe.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceOperation.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/XmlUtils.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jms/actions/AddJmsConfigAction.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jms/actions/ReplaceMdbAnnotationWithJmsListener.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jsf/actions/AddJoinfacesDependencies.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jsf/conditions/IsMigrateJsf2ToSpringBootApplicableCondition.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootAction.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/utils/AnnotationUtils.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/web/MigrateServletDefinitionFromWebXmlToAnnotation.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/web/conditions/ShouldAddServletComponentScan.java create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/documentation-actions.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/mark-and-clean-remote-ejbs.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/migrate-annotated-servlets.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/migrate-jms.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/migrate-jndi-lookup.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/migrate-jpa-to-spring-boot.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/migrate-jsf-2.x-to-spring-boot.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/migrate-stateless-ejb.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/recipes/migrate-tx-to-spring-boot.yaml create mode 100644 components/jaxrs-recipes/src/main/resources/templates/eclipselink-configuration-class.ftl create mode 100644 components/jaxrs-recipes/src/main/resources/templates/jaxws-endpoint.ftl create mode 100644 components/jaxrs-recipes/src/main/resources/templates/jaxws-web-config.ftl create mode 100644 components/jaxrs-recipes/src/main/resources/templates/jms-config.ftl create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/AddNewDependencyTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/architecture/ControlledInstantiationOfExecutionContextTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbAnnotationsTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookupTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/StatelessAnnotationTemplateMapperTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/recipes/EjbJarDeploymentDescriptorTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/recipes/MigrateStatelessEjbRecipeTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotationsTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CacheControlTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ConvertJaxRsAnnotationsTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusFamilyTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttpHeadersTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/GenerateWebServicesTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptorTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/WebServiceOperationTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/AddJmsConfigTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/AddJoinfacesDependencies_Mojarra_Test.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/AddJoinfacesDependencies_MyFaces_Test.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/MigrateJsf2xToSpringBootRecipeTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/recipes/MigrateTxToSpringBootRecipeTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/web/conditions/ShouldAddServletComponentScanTest.java create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/web/recipes/MigrateAnnotatedServletRecipeTest.java create mode 100644 components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/pom.xml create mode 100644 components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/src/main/java/com/example/SomeClass.java create mode 100644 components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/src/main/resources/META-INF/persistence.xml create mode 100644 components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/pom.xml create mode 100644 components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/src/main/java/com/example/SomeClass.java create mode 100644 components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/src/main/resources/META-INF/persistence.xml create mode 100644 components/jaxrs-recipes/testcode/jee/web/given/pom.xml create mode 100644 components/jaxrs-recipes/testcode/jee/web/given/src/main/java/com/examples/jee/web/TheServlet.java create mode 100644 components/jaxrs-recipes/testcode/jee/web/given/src/main/webapp/WEB-INF/web.xml create mode 100644 components/jaxrs-recipes/testcode/wsdl/calculator.wsdl diff --git a/components/jaxrs-recipes/pom.xml b/components/jaxrs-recipes/pom.xml new file mode 100644 index 000000000..adb16aad8 --- /dev/null +++ b/components/jaxrs-recipes/pom.xml @@ -0,0 +1,245 @@ + + + + + 4.0.0 + + + + + + + + org.springframework.sbm + jaxrs-recipes + 0.15.2-SNAPSHOT + jar + + + UTF-8 + UTF-8 + 2.5.0 + 17 + 17 + 3.11.0 + 17 + 2.1.14 + 8.29.0 + 4.32.0 + 1.17.0 + 3.3.1 + 0.10.0 + 1.19.1 + 3.2.0 + 3.1.4 + 1.18.30 + UTF-8 + UTF-8 + src/generated/java + 1.33 + 0.0.7 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.openrewrite + rewrite-core + ${openrewrite.version} + + + org.openrewrite + rewrite-java + ${openrewrite.version} + + + + + org.springframework.sbm + sbm-core + ${project.version} + + + org.openrewrite + rewrite-java + + + org.openrewrite + rewrite-core + + + + + + + + + + + com.tngtech.archunit + archunit-junit5 + 1.2.0 + + + org.springframework.sbm + sbm-support-jee + 0.15.2-SNAPSHOT + + + org.springframework.sbm + sbm-core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.aspectj + aspectjweaver + 1.9.7 + + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + org.glassfish.jaxb + jaxb-runtime + 2.3.3 + + + + org.springframework.sbm + test-helper + ${project.version} + test + + + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.sbm + recipe-test-support + ${project.version} + test + + + org.springframework.sbm + sbm-core + + + + + org.springframework.sbm + sbm-core + 0.15.2-SNAPSHOT + tests + test + + + org.openrewrite + rewrite-core + 8.29.0 + compile + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java new file mode 100644 index 000000000..49a01a143 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java @@ -0,0 +1,123 @@ +/* + * Copyright 2021 - 2023 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 example.recipe; + + +import org.jetbrains.annotations.NotNull; +import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; +import org.springframework.rewrite.parser.JavaParserBuilder; +import org.springframework.rewrite.resource.ProjectResourceSet; +import org.springframework.rewrite.resource.ProjectResourceSetFactory; +import org.springframework.rewrite.resource.RewriteMigrationResultMerger; +import org.springframework.rewrite.resource.RewriteSourceFileWrapper; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.context.ProjectContextFactory; +import org.springframework.sbm.java.refactoring.JavaRefactoringFactory; +import org.springframework.sbm.java.refactoring.JavaRefactoringFactoryImpl; +import org.springframework.sbm.java.util.BasePackageCalculator; +import org.springframework.sbm.jee.jaxrs.actions.ConvertJaxRsAnnotations; +import org.springframework.sbm.project.resource.ProjectResourceSetHolder; +import org.springframework.sbm.project.resource.ProjectResourceWrapper; +import org.springframework.sbm.project.resource.ProjectResourceWrapperRegistry; +import org.springframework.sbm.project.resource.SbmApplicationProperties; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Fabian Krüger + */ +public class SbmAdapterRecipe extends Recipe { + + + + @Override + public @NlsRewrite.DisplayName String getDisplayName() { + return ""; + } + + @Override + public @NlsRewrite.Description String getDescription() { + return ""; + } + + public SbmAdapterRecipe() { + + } + + @Override + public TreeVisitor getVisitor() { + return new TreeVisitor() { + + private ProjectResourceSetFactory projectResourceSetFactory; + private ProjectContextFactory projectContextFactory; + + @Override + public void visit(@Nullable List nodes, ExecutionContext executionContext) { + + // create the required classes + initBeans(executionContext); + + // transform nodes to SourceFiles + List sourceFiles = nodes.stream().filter(SourceFile.class::isInstance).map(SourceFile.class::cast).toList(); + + // FIXME: base dir calculation is fake + Path baseDir = executionContext.getMessage("base.dir"); + + // Create the SBM resource set abstraction + ProjectResourceSet projectResourceSet = projectResourceSetFactory.create(baseDir, sourceFiles); + // Create the SBM ProjectContext + ProjectContext pc = projectContextFactory.createProjectContext(baseDir, projectResourceSet); + + // Execute the SBM Action = the JAXRS Recipe + new ConvertJaxRsAnnotations().apply(pc); + + // Merge back result + List modifiedNodes = merge(nodes, pc.getProjectResources()); + + // Process other + super.visit(modifiedNodes, executionContext); + } + + private void initBeans(ExecutionContext executionContext) { + RewriteSourceFileWrapper sourceFileWrapper = new RewriteSourceFileWrapper(); + SbmApplicationProperties sbmApplicationProperties = new SbmApplicationProperties(); + JavaParserBuilder parserBuilder = new JavaParserBuilder(); + List projectResourceWrappers = new ArrayList<>(); + + RewriteMigrationResultMerger merger = new RewriteMigrationResultMerger(sourceFileWrapper); + ProjectResourceSetHolder holder = new ProjectResourceSetHolder(executionContext, merger); + + projectResourceSetFactory = new ProjectResourceSetFactory(new RewriteMigrationResultMerger(sourceFileWrapper), sourceFileWrapper, executionContext); + ProjectResourceWrapperRegistry registry = new ProjectResourceWrapperRegistry(projectResourceWrappers); + JavaRefactoringFactory refactoringFactory = new JavaRefactoringFactoryImpl(holder, executionContext); + BasePackageCalculator calculator = new BasePackageCalculator(sbmApplicationProperties); + + ProjectResourceSetFactory resourceSetFactory = new ProjectResourceSetFactory(merger, sourceFileWrapper, executionContext); + + projectContextFactory = new ProjectContextFactory(registry, holder, refactoringFactory, calculator, parserBuilder, executionContext, merger, resourceSetFactory); + } + }; + } + + private List merge(List nodes, ProjectResourceSet projectResources) { + // merge the changed results into the given list and return the result + return new ArrayList<>(); + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java new file mode 100644 index 000000000..ce08240a8 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 - 2023 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.java.migration.conditions; + +import lombok.*; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.Condition; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class HasTypeAnnotation implements Condition { + + @Setter + @Getter + private String annotation; + + @Override + public String getDescription() { + return "If there are any types annotated with " + annotation; + } + + @Override + public boolean evaluate(ProjectContext context) { + return true; +// context.getProjectJavaSources().asStream() +// .flatMap(js -> js.getTypes().stream()) +// .anyMatch(t -> t.hasAnnotation(annotation)); + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.java new file mode 100644 index 000000000..0aeb36b54 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021 - 2023 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.java.migration.recipes; + +import lombok.RequiredArgsConstructor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.AddImport; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.MethodInvocation; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; + +import java.util.function.Consumer; +import java.util.function.Predicate; + +@RequiredArgsConstructor +public class RewriteMethodInvocation extends Recipe { + + final private Predicate checkMethodInvocation; + final private Transformer transformer; + + @Override + public String getDisplayName() { + return "Rewritre method invocation"; + } + + @Override + protected TreeVisitor getVisitor() { + return new JavaVisitor() { + + @Override + public J visitMethodInvocation(MethodInvocation method, ExecutionContext p) { + MethodInvocation m = (MethodInvocation) super.visitMethodInvocation(method, p); + if (checkMethodInvocation.test(m)) { + return transformer.transform(this, m, this::addImport); + } + return m; + } + + private void addImport(String fqName) { + AddImport op = new AddImport<>(fqName, null, false); + if (!getAfterVisit().contains(op)) { + doAfterVisit(op); + } + } + }; + } + + public static Predicate methodInvocationMatcher(String signature) { + MethodMatcher methodMatcher = new MethodMatcher(signature); + return mi -> methodMatcher.matches(mi); + } + + public interface Transformer { + + J transform(JavaVisitor visitor, MethodInvocation currentInvocation, Consumer addImport); + + } + + public static RewriteMethodInvocation renameMethodInvocation(Predicate matcher, String newName, String newType) { + JavaType type = JavaType.buildType(newType); + return new RewriteMethodInvocation(matcher, (v, m, a) -> { + return m.withName(m.getName().withSimpleName(newName)) + .withMethodType(m.getMethodType().withReturnType(type)) + .withDeclaringType(TypeUtils.asFullyQualified(type)); + }); + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java new file mode 100644 index 000000000..5b8c8a552 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java @@ -0,0 +1,129 @@ +/* + * Copyright 2021 - 2023 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.java.migration.recipes.openrewrite; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; + +@Value +@EqualsAndHashCode(callSuper = false) +// TODO: Remove this class and use the one in Openrewrite core once we use 7.39.1 or later +public class ReplaceConstantWithAnotherConstant extends Recipe { + + @Option(displayName = "Fully qualified name of the constant to replace", example = "org.springframework.http.MediaType.APPLICATION_JSON_VALUE") + String existingFullyQualifiedConstantName; + + @Option(displayName = "Fully qualified name of the constant to use in place of existing constant", example = "org.springframework.http.MediaType.APPLICATION_JSON_VALUE") + String fullyQualifiedConstantName; + + @Override + public String getDisplayName() { + return "Replace constant with another constant"; + } + + @Override + public String getDescription() { + return "Replace constant with another constant, adding/removing import on class if needed."; + } + + @Override + protected TreeVisitor getSingleSourceApplicableTest() { + return new UsesType<>(existingFullyQualifiedConstantName.substring(0,existingFullyQualifiedConstantName.lastIndexOf('.'))); + } + + @Override + public JavaVisitor getVisitor() { + return new ReplaceConstantWithAnotherConstantVisitor(existingFullyQualifiedConstantName, fullyQualifiedConstantName); + } + + private static class ReplaceConstantWithAnotherConstantVisitor extends JavaVisitor { + + private final String existingOwningType; + private final String constantName; + private final String owningType; + private final String template; + + public ReplaceConstantWithAnotherConstantVisitor(String existingFullyQualifiedConstantName, String fullyQualifiedConstantName) { + this.existingOwningType = existingFullyQualifiedConstantName.substring(0, existingFullyQualifiedConstantName.lastIndexOf('.')); + this.constantName = existingFullyQualifiedConstantName.substring(existingFullyQualifiedConstantName.lastIndexOf('.') + 1); + this.owningType = fullyQualifiedConstantName.substring(0, fullyQualifiedConstantName.lastIndexOf('.')); + this.template = fullyQualifiedConstantName.substring(owningType.lastIndexOf('.') + 1); + } + + @Override + public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext executionContext) { + if (isConstant(fieldAccess.getName().getFieldType())) { + maybeRemoveImport(existingOwningType); + if (existingOwningType.contains("$")) { + maybeRemoveImport(existingOwningType.substring(0, existingOwningType.indexOf('$'))); + } + maybeAddImport(owningType, false); + return fieldAccess + .withTemplate( + JavaTemplate.builder(this::getCursor, template).imports(owningType).build(), + fieldAccess.getCoordinates().replace()) + .withPrefix(fieldAccess.getPrefix()); + } + return super.visitFieldAccess(fieldAccess, executionContext); + } + + @Override + public J visitIdentifier(J.Identifier ident, ExecutionContext executionContext) { + if (isConstant(ident.getFieldType()) && !isVariableDeclaration()) { + maybeRemoveImport(existingOwningType); + maybeAddImport(owningType, false); + return ident + .withTemplate( + JavaTemplate.builder(this::getCursor, template).imports(owningType).build(), + ident.getCoordinates().replace()) + .withPrefix(ident.getPrefix()); + } + return super.visitIdentifier(ident, executionContext); + } + + private boolean isConstant(@Nullable JavaType.Variable varType) { + return varType != null && TypeUtils.isOfClassType(varType.getOwner(), existingOwningType) && + varType.getName().equals(constantName); + } + + private boolean isVariableDeclaration() { + Cursor maybeVariable = getCursor().dropParentUntil(is -> is instanceof J.VariableDeclarations || is instanceof J.CompilationUnit); + if (!(maybeVariable.getValue() instanceof J.VariableDeclarations)) { + return false; + } + JavaType.Variable variableType = ((J.VariableDeclarations) maybeVariable.getValue()).getVariables().get(0).getVariableType(); + if (variableType == null) { + return true; + } + + JavaType.FullyQualified ownerFqn = TypeUtils.asFullyQualified(variableType.getOwner()); + if (ownerFqn == null) { + return true; + } + + return constantName.equals(((J.VariableDeclarations) maybeVariable.getValue()).getVariables().get(0).getSimpleName()) && + existingOwningType.equals(ownerFqn.getFullyQualifiedName()); + } + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbAnnotations.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbAnnotations.java new file mode 100644 index 000000000..9842ca1e1 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbAnnotations.java @@ -0,0 +1,138 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.*; +import org.springframework.sbm.engine.context.ProjectContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Migrates {@code @EJB} annotations to {@code @Autowired}. + */ +public class MigrateEjbAnnotations extends AbstractAction { + + public static final String EJB_ANNOTATION = "javax.ejb.EJB"; + private static final String AUTOWIRED_ANNOTATION = "org.springframework.beans.factory.annotation.Autowired"; + private static final String QUALIFIER_ANNOTATION = "org.springframework.beans.factory.annotation.Qualifier"; + + @Override + public void apply(ProjectContext context) { + context.getProjectJavaSources().list().stream() + .map(JavaSource::getTypes) + .flatMap(List::stream) + .forEach(this::migrateEJBAnnotations); + } + + private void migrateEJBAnnotations(Type type) { + + // if a setter is annotated migrate it and annotate the matching member + List fieldsWithMethodInjection = new ArrayList<>(); + type.getMethods().stream() + .filter(method -> method.getName().startsWith("set")) + .filter(method -> method.hasAnnotation(EJB_ANNOTATION)) + .forEach(method -> { + String affectedMemberName = this.migrateEjbAnnotation(method); + fieldsWithMethodInjection.add(affectedMemberName); + }); + + type.getMembers().stream() + .filter(member -> member.hasAnnotation(EJB_ANNOTATION)) + .filter(member -> !fieldsWithMethodInjection.contains(member.getName())) + .forEach(member -> this.migrateEjbAnnotation(member)); + + } + + private String migrateEjbAnnotation(Method method) { + Annotation annotation = method.getAnnotation(EJB_ANNOTATION).get(); + + String autowiredAnnotationString = renderAutowiredAnnotation(annotation); + method.removeAnnotation(annotation); + method.addAnnotation(autowiredAnnotationString, AUTOWIRED_ANNOTATION); + + Optional qualifierAnnotation = buildQualifierAnnotation(annotation); + if (qualifierAnnotation.isPresent()) { + method.addAnnotation(qualifierAnnotation.get(), QUALIFIER_ANNOTATION); + } + String memberName = method.getName().substring(3); + memberName = Character.toLowerCase(memberName.charAt(0)) + memberName.substring(1); + return memberName; + } + + private void migrateEjbAnnotation(Member member) { + Annotation annotation = member.getAnnotation(EJB_ANNOTATION); + + String autowiredAnnotationString = renderAutowiredAnnotation(annotation); + member.removeAnnotation(annotation); + member.addAnnotation(autowiredAnnotationString, AUTOWIRED_ANNOTATION); + + Optional qualifierAnnotation = buildQualifierAnnotation(annotation); + if (qualifierAnnotation.isPresent()) { + member.addAnnotation(qualifierAnnotation.get(), QUALIFIER_ANNOTATION); + } + } + + private Optional buildQualifierAnnotation(Annotation annotation) { + if (annotation.getAttribute("beanName") != null) { + StringBuilder stringBuilder = new StringBuilder(); + String beanName = annotation.getAttribute("beanName").getAssignmentRightSide().printVariable(); + stringBuilder.append("@Qualifier(").append("\"").append(beanName).append("\"").append(")"); + return Optional.of(stringBuilder.toString()); + } + return Optional.empty(); + } + + private String renderAutowiredAnnotation(Annotation annotation) { + StringBuilder autowiredSb = new StringBuilder(); + Optional comment = renderComment(annotation); + + if (comment.isPresent()) { + autowiredSb.append(comment.get()); + } + autowiredSb.append("@Autowired"); + return autowiredSb.toString(); + } + + private Optional renderComment(Annotation annotation) { + List commentLines = new ArrayList<>(); + StringBuilder autowiredSb = new StringBuilder(); + + if (annotation.getAttribute("description") != null) { + String description = annotation.getAttribute("description").getAssignmentRightSide().printVariable(); + commentLines.add(description.trim()); + } + if (annotation.getAttribute("lookup") != null) { + String lookup = annotation.getAttribute("lookup").getAssignmentRightSide().printVariable(); + commentLines.add("SBM-TODO: lookup was '" + lookup.trim() + "'"); + } + if (annotation.getAttribute("beanInterface") != null) { + String beanInterface = annotation.getAttribute("beanInterface").getAssignmentRightSide().printVariable(); + commentLines.add("SBM-TODO: beanInterface was '" + beanInterface.trim() + "'"); + } + if (!commentLines.isEmpty()) { + autowiredSb.append("/*\n"); + commentLines.forEach(c -> autowiredSb.append(" * ").append(c).append("\n")); + autowiredSb.append(" */\n"); + return Optional.of(autowiredSb.toString()); + } + + return Optional.empty(); + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptor.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptor.java new file mode 100644 index 000000000..9559483db --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptor.java @@ -0,0 +1,188 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.Type; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.jee.ejb.api.DescriptionType; +import org.springframework.sbm.jee.ejb.api.SessionBeanType; +import org.springframework.sbm.jee.ejb.api.EjbJarXml; +import org.springframework.sbm.jee.ejb.filter.EjbJarXmlResourceFinder; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@NoArgsConstructor +@SuperBuilder +public class MigrateEjbDeploymentDescriptor extends AbstractAction { + @Override + public void apply(ProjectContext context) { + Optional ejbJarXml = context.search(new EjbJarXmlResourceFinder()); + if (ejbJarXml.isPresent()) { + migrateEjbDeploymentDescriptor(context, ejbJarXml.get()); + } + } + + private void migrateEjbDeploymentDescriptor(ProjectContext context, EjbJarXml ejbJarXml) { + + List sessionBeansToRemove = new ArrayList<>(); + + if (ejbJarXml.getEjbJarXml().getEnterpriseBeans() != null) { + ejbJarXml.getEjbJarXml().getEnterpriseBeans().getSessionOrEntityOrMessageDriven() + .forEach(bean -> { + if (bean.getClass().isAssignableFrom(SessionBeanType.class)) { + SessionBeanType sbt = (SessionBeanType) bean; + String sessionBeanType = sbt.getSessionType().getValue(); + if ("Stateless".equals(sessionBeanType)) { + handleStatelessSessionBean(context, sbt); + // FIXME: #469 + sessionBeansToRemove.add(sbt); + } + } + }); + } + ejbJarXml.removeSessionBeans(sessionBeansToRemove); + + ejbJarXml.getEjbJarXml().getEnterpriseBeans().getSessionOrEntityOrMessageDriven() + .forEach(bean -> { + if (bean.getClass().isAssignableFrom(SessionBeanType.class)) { + SessionBeanType sbt = (SessionBeanType) bean; + ejbJarXml.removeSessionBean(sbt); + } + }); + + + // delete deployment descriptor when empty + if (ejbJarXml.isEmpty()) { + ejbJarXml.delete(); + } + } + + private void handleStatelessSessionBean(ProjectContext context, SessionBeanType sbt) { + String fqName = sbt.getEjbClass().getValue(); + Optional javaSourceDeclaringType = context.getProjectJavaSources().findJavaSourceDeclaringType(fqName); + if (javaSourceDeclaringType.isPresent()) { + JavaSource js = javaSourceDeclaringType.get(); + Type type = js.getType(fqName); + + // remove existing @Stateless annotation if exists + String statelessAnnotationFqName = "javax.ejb.Stateless"; + if (type.hasAnnotation(statelessAnnotationFqName)) { + type.removeAnnotation(statelessAnnotationFqName); + } + + // add @Stateless annotation as defined in deployment descriptor + final StringBuilder statelessSnippet = + new StringBuilder("@Stateless(") + .append(getEjbName(sbt) + .map(n -> "name = \"" + n + "\"") + .orElse("") + ).append(getMappedName(sbt) + .map(m -> ", mappedName = \"" + m + "\"") + .orElse("") + ).append(getDescription(sbt) + .map(d -> ", description = \"" + d + "\"") + .orElse("") + ).append(")"); + + if(statelessSnippet.length() == "@Statlesss()".length()) + statelessSnippet.deleteCharAt("@Statlesss".length() - 1); + + type.addAnnotation(statelessSnippet.toString(), "javax.ejb.Stateless"); + + getRemote(sbt) + .map( r -> "@Remote(" + r + ".class)") + .ifPresent( a -> type.addAnnotation(a,"javax.ejb.Remote")); + + getRemoteHome(sbt) + .map( r -> "@RemoteHome(" + r + ".class)") + .ifPresent( a -> type.addAnnotation(a,"javax.ejb.RemoteHome")); + + getLocal(sbt) + .map( r -> "@Local(" + r + ".class)") + .ifPresent( a -> type.addAnnotation(a,"javax.ejb.Local")); + + getLocalHome(sbt) + .map( r -> "@LocalHome(" + r + ".class)") + .ifPresent( a -> type.addAnnotation(a,"javax.ejb.LocalHome")); + + getTransactionType(sbt) + .map( r -> "@TransactionManagement(javax.ejb.TransactionManagementType." + r.toUpperCase() + ")") + .ifPresent( a -> type.addAnnotation(a,"javax.ejb.TransactionManagement")); + + } else { + throw new RuntimeException("Could not find any Java file declaring type '" + fqName + "'"); + } + } + + private Optional getEjbName(SessionBeanType sbt) { + return sbt.getEjbName() != null && !sbt.getEjbName().getValue().isEmpty() + ? Optional.ofNullable(sbt.getEjbName().getValue()) + : Optional.empty(); + } + private Optional getMappedName(SessionBeanType sbt){ + return sbt.getMappedName() != null && !sbt.getMappedName().getValue().isEmpty() + ? Optional.ofNullable(sbt.getMappedName().getValue()) + : Optional.empty(); + + } + + //FIXME Add support for other languages in description. Currently description attribute of + // @Stateless annotation is not a list + private Optional getDescription(SessionBeanType sbt) { + return sbt.getDescription() != null ? sbt.getDescription() + .stream() + .filter( d ->"en".equalsIgnoreCase(d.getLang())) + .map(DescriptionType::getValue) + .findFirst() + : Optional.empty(); + } + + private Optional getRemote(SessionBeanType sbt){ + return sbt.getRemote() != null && !sbt.getRemote().getValue().isEmpty() + ? Optional.ofNullable(sbt.getRemote().getValue()) + : Optional.empty(); + } + + private Optional getRemoteHome(SessionBeanType sbt){ + return sbt.getHome() != null && !sbt.getHome().getValue().isEmpty() + ? Optional.ofNullable(sbt.getHome().getValue()) + : Optional.empty(); + } + + private Optional getLocal(SessionBeanType sbt){ + return sbt.getLocal() != null && !sbt.getLocal().getValue().isEmpty() + ? Optional.ofNullable(sbt.getLocal().getValue()) + : Optional.empty(); + } + + private Optional getLocalHome(SessionBeanType sbt){ + return sbt.getLocalHome() != null && !sbt.getLocalHome().getValue().isEmpty() + ? Optional.ofNullable(sbt.getLocalHome().getValue()) + : Optional.empty(); + } + + private Optional getTransactionType(SessionBeanType sbt){ + return sbt.getTransactionType() != null && !sbt.getTransactionType().getValue().isEmpty() + ? Optional.ofNullable(sbt.getTransactionType().getValue()) + : Optional.empty(); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java new file mode 100644 index 000000000..4803f64b9 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java @@ -0,0 +1,191 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.rewrite.recipes.GenericOpenRewriteRecipe; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.engine.context.ProjectContext; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.java.AddImport; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.RemoveUnusedImports; +import org.openrewrite.java.cleanup.RemoveUnusedLocalVariables; +import org.openrewrite.java.format.AutoFormat; +import org.openrewrite.java.tree.*; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class MigrateJndiLookup extends AbstractAction { + + @Override + public void apply(ProjectContext context) { + context.getProjectJavaSources().list().stream() + .filter(js -> js.hasImportStartingWith("javax.naming.InitialContext")) + .forEach(sourceWithLookup -> { + migrateJndiLookup(sourceWithLookup); + }); + } + + private void migrateJndiLookup(JavaSource sourceWithLookup) { + Recipe recipe = new GenericOpenRewriteRecipe<>(() -> new MigrateJndiLookupVisitor()) + .doNext(new RemoveUnusedLocalVariables(null)) + .doNext(new RemoveUnusedImports()) + .doNext(new GenericOpenRewriteRecipe<>(() -> new AddImport<>("org.springframework.beans.factory.annotation.Autowired", null, false))) + .doNext(new AutoFormat()); + + sourceWithLookup.apply(recipe); + } + + class MigrateJndiLookupVisitor extends JavaIsoVisitor { + + public static final String MATCHES_KEY = "matchingVariables"; + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration cd, ExecutionContext executionContext) { + + J.ClassDeclaration classDeclaration = super.visitClassDeclaration(cd, executionContext); + + Object matchingVariable = executionContext.getMessage(MATCHES_KEY); + if (matchingVariable != null) { + List matches = (List) matchingVariable; + Iterator iterator = matches.iterator(); + while (iterator.hasNext()) { + MatchFound nextMatch = iterator.next(); + classDeclaration = addInstanceAsAutowiredMember(classDeclaration, nextMatch); + } + } + + return classDeclaration; + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) { + if (isAssignedThroughStaticInitialContextLookup(multiVariable)) { + J.Block containingBlock = findContainingBlock(); + addToExecutionContext(containingBlock, multiVariable, executionContext); + return super.visitVariableDeclarations(multiVariable, executionContext); + } + return multiVariable; + } + + @Override + public J.Block visitBlock(J.Block b, ExecutionContext executionContext) { + J.Block block = super.visitBlock(b, executionContext); + + if (executionContext.getMessage(MATCHES_KEY) != null) { + List matches = executionContext.getMessage(MATCHES_KEY); + block = removeFromMethodBlock(matches, block); + } + + return block; + } + + private J.Block findContainingBlock() { + return getCursor().firstEnclosing(J.Block.class); + } + + private void addToExecutionContext(J.Block containingBlock, J.VariableDeclarations multiVariable, ExecutionContext executionContext) { + MatchFound matchFound = new MatchFound(containingBlock, multiVariable); + if (executionContext.getMessage(MATCHES_KEY) != null) { + ((List) executionContext.getMessage(MATCHES_KEY)).add(matchFound); + } else { + List matches = new ArrayList<>(); + matches.add(matchFound); + executionContext.putMessage(MATCHES_KEY, matches); + } + } + + + private J.Block removeFromMethodBlock(List matches, J.Block block) { + for (MatchFound match : matches) { + if (match.getContainingBlock() != null && block.getId().equals(match.getContainingBlock().getId())) { + List statements = block.getStatements(); + Iterator iterator = statements.iterator(); + while (iterator.hasNext()) { + Statement statement = iterator.next(); + if (statement == match.getMultiVariable()) { + iterator.remove(); + block = block.withStatements(statements); + } + } + } else if (match.getContainingBlock() == null) { + + } + } + return block; + } + + private J.ClassDeclaration addInstanceAsAutowiredMember(J.ClassDeclaration classDecl, MatchFound matchFound) { + J.Block body = classDecl.getBody(); + J.VariableDeclarations variable = matchFound.getMultiVariable(); + JavaType.Class type = (JavaType.Class) variable.getTypeExpression().getType(); + String variableName = variable.getVariables().get(0).getSimpleName(); + JavaTemplate javaTemplate = JavaTemplate.builder(() -> getCursor(), "@Autowired\nprivate " + type.getClassName() + " " + variableName).build(); + J.Block result = body.withTemplate(javaTemplate, body.getCoordinates().lastStatement()); + List statements1 = result.getStatements(); + Statement statement = statements1.get(statements1.size() - 1); + statements1.remove(statement); + statements1.add(0, statement); + result = body.withStatements(statements1); + J.ClassDeclaration classDeclaration = classDecl.withBody(result); + return classDeclaration; + } + + private boolean isAssignedThroughStaticInitialContextLookup(J.VariableDeclarations multiVariable) { + if (multiVariable.getVariables().size() != 1) { + // TODO: handle multi variable -> maybe refactor multivariable to single variable decl first. + } else { + JRightPadded namedVariable = multiVariable.getPadding().getVariables().get(0); + JLeftPadded variableInitializer = namedVariable.getElement().getPadding().getInitializer(); + if (variableInitializer.getClass().isAssignableFrom(JLeftPadded.class)) { + JLeftPadded jLeftPadded = variableInitializer; + Expression element = variableInitializer.getElement(); + if (element.getClass().isAssignableFrom(J.TypeCast.class)) { + J.MethodInvocation methodInvocation = (J.MethodInvocation) ((J.TypeCast) element).getExpression(); + if (methodInvocation.getSelect().getType().getClass().isAssignableFrom(JavaType.Class.class)) { + JavaType.Class type = (JavaType.Class) methodInvocation.getSelect().getType(); + return type.getFullyQualifiedName().equals("javax.naming.InitialContext"); + } + } else if (element.getClass().isAssignableFrom(J.MethodInvocation.class)) { + J.MethodInvocation methodInvocation = (J.MethodInvocation) element; + if (methodInvocation.getSelect().getType().getClass().isAssignableFrom(JavaType.Class.class)) { + JavaType.Class type = (JavaType.Class) methodInvocation.getSelect().getType(); + return type.getFullyQualifiedName().equals("javax.naming.InitialContext"); + } + } + } + + } + return false; + } + + @RequiredArgsConstructor + @Getter + private class MatchFound { + private final J.Block containingBlock; + private final J.VariableDeclarations multiVariable; + } + } + + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeans.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeans.java new file mode 100644 index 000000000..e97475991 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeans.java @@ -0,0 +1,84 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import lombok.RequiredArgsConstructor; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.SuperTypeHierarchy; +import org.springframework.sbm.java.api.Type; + +import java.util.List; + +/** + * Migrate local stateless Session Beans to Spring beans annotated with {@code @Service}. + * + *

+ *

    + *
  • For NoInterfaceView Session Beans the {@code @Stateless} annotation is replaced with {@code @Service}.
  • + *
  • For all other Session Beans not implementing a {@code @Remote} business interface
  • + *
      + *
    • the {@code @Local} interface information is removed.
    • + *
    • the {@code @Stateless} annotation is replaced with {@code @Service}.
    • + *
    + *
  • Session Beans implementing a {@code @Remote} interface are ignored.
  • + *
+ *

+ */ +@RequiredArgsConstructor +public class MigrateLocalStatelessSessionBeans extends AbstractAction { + + private final MigrateLocalStatelessSessionBeansHelper helper; + + public MigrateLocalStatelessSessionBeans() { + helper = new MigrateLocalStatelessSessionBeansHelper(); + } + + @Override + public void apply(ProjectContext context) { + migrateLocalStatelessSessionBeans(context); + migrateSingletonSessionBeans(context); + removeLocalBeanAnnotations(context); + } + + private void removeLocalBeanAnnotations(ProjectContext context) { + helper.removeLocalBeanAnnotations(context.getProjectJavaSources()); + } + + private void migrateSingletonSessionBeans(ProjectContext context) { + List singletonBeans = helper.findTypesAnnotatedWithSingleton(context.getProjectJavaSources()); + singletonBeans.forEach(ssb -> { + Type type = ssb.getType(); + helper.migrateSingletonAnnotation(type); + }); + } + + private void migrateLocalStatelessSessionBeans(ProjectContext context) { + List statelessSessionBeans = helper.findTypesAnnotatedWithStateless(context.getProjectJavaSources()); + + statelessSessionBeans.forEach(ssb -> { + Type type = ssb.getType(); + JavaSource javaSource = ssb.getJavaSource(); + SuperTypeHierarchy hierarchy = new SuperTypeHierarchy(type); + if (helper.implementsNoRemoteInterface(hierarchy)) { + helper.removeLocalAnnotations(hierarchy); + helper.migrateStatelessAnnotation(type); + } + }); + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansHelper.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansHelper.java new file mode 100644 index 000000000..529854cab --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansHelper.java @@ -0,0 +1,114 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.java.api.*; + +import java.util.ArrayList; +import java.util.List; + +public class MigrateLocalStatelessSessionBeansHelper { + + public static final String EJB_STATELESS_ANNOTATION = "javax.ejb.Stateless"; + public static final String EJB_LOCAL_ANNOTATION = "javax.ejb.Local"; + private static final String SPRING_SERVICE_ANNOTATION = "org.springframework.stereotype.Service"; + private static final String EJB_REMOTE_ANNOTATION = "javax.ejb.Remote"; + private static final String EJB_SINGLETON_ANNOTATION = "javax.ejb.Singleton"; + private static final String EJB_LOCALBEAN_ANNOTATION = "javax.ejb.LocalBean"; + + public boolean implementsNoRemoteInterface(SuperTypeHierarchy hierarchy) { + boolean noRemoteInterface = new AnnotatedTypeFinder(hierarchy, EJB_REMOTE_ANNOTATION).findAnnotatedTypes().isEmpty(); + return noRemoteInterface || hierarchy.getRoot().getSuperTypes().isEmpty(); + } + + public void removeLocalAnnotations(SuperTypeHierarchy hierarchy) { + List annotatedTypes = new AnnotatedTypeFinder(hierarchy, EJB_LOCAL_ANNOTATION).findAnnotatedTypes(); + annotatedTypes.forEach(type -> type.removeAnnotation(EJB_LOCAL_ANNOTATION)); + } + + public List findTypesAnnotatedWithStateless(ProjectJavaSources javaSourceSet) { + return getTypeAndSourceFiles(javaSourceSet, EJB_STATELESS_ANNOTATION); + } + + private List getTypeAndSourceFiles(ProjectJavaSources javaSourceSet, String annotation) { + List typeAndSourceFiles = new ArrayList<>(); + javaSourceSet.list().stream() + .forEach(js -> { + js.getTypes().stream() + .filter(t -> { + return t.hasAnnotation(annotation); + }) + .forEach(t -> typeAndSourceFiles.add(new TypeAndSourceFile(js, t))); + }); + return typeAndSourceFiles; + } + + public void migrateStatelessAnnotation(Type ssb) { + StatelessAnnotationTemplateMapper statelessAnnotationTemplateMapper = new StatelessAnnotationTemplateMapper(); + Annotation annotation = ssb.getAnnotations().stream().filter(a -> EJB_STATELESS_ANNOTATION.equals(a.getFullyQualifiedName())).findFirst().get(); + String annotationSnippet = statelessAnnotationTemplateMapper.mapToServiceAnnotation(annotation); + ssb.removeAnnotation(EJB_STATELESS_ANNOTATION); + ssb.addAnnotation(annotationSnippet, SPRING_SERVICE_ANNOTATION); + } + + public List findTypesAnnotatedWithSingleton(ProjectJavaSources javaSourceSet) { + return getTypeAndSourceFiles(javaSourceSet, EJB_SINGLETON_ANNOTATION); + } + + public void migrateSingletonAnnotation(Type ssb) { + SingletonAnnotationTemplateMapper statelessAnnotationTemplateMapper = new SingletonAnnotationTemplateMapper(); + Annotation annotation = ssb.getAnnotations().stream().filter(a -> EJB_SINGLETON_ANNOTATION.equals(a.getFullyQualifiedName())).findFirst().get(); + String annotationSnippet = statelessAnnotationTemplateMapper.mapToServiceAnnotation(annotation); + ssb.removeAnnotation(EJB_SINGLETON_ANNOTATION); + ssb.addAnnotation(annotationSnippet, SPRING_SERVICE_ANNOTATION); + } + + public List findTypesAnnotatedWithLocalBean(ProjectJavaSources javaSourceSet) { + return getTypeAndSourceFiles(javaSourceSet, EJB_LOCALBEAN_ANNOTATION); + } + + public void removeLocalBeanAnnotations(ProjectJavaSources javaSourceSet) { + findTypesAnnotatedWithLocalBean(javaSourceSet) + .forEach(ts -> ts.getType().removeAnnotation(EJB_LOCALBEAN_ANNOTATION)); + } + + private class AnnotatedTypeFinder { + + private final SuperTypeHierarchy superTypeHierarchy; + private final String annotationToFind; + private final List annotatedTypes = new ArrayList<>(); + + public AnnotatedTypeFinder(SuperTypeHierarchy superTypeHierarchy, String annotationToFind) { + this.superTypeHierarchy = superTypeHierarchy; + this.annotationToFind = annotationToFind; + } + + public List findAnnotatedTypes() { + traverseHierarchyUpAndSearchForAnnotation(superTypeHierarchy.getRoot()); + return annotatedTypes; + } + + private void traverseHierarchyUpAndSearchForAnnotation(SuperTypeHierarchyNode currentNode) { + if (currentNode.getNode().hasAnnotation(annotationToFind)) { + this.annotatedTypes.add(currentNode.getNode()); + } + if (!currentNode.getSuperTypes().isEmpty()) { + currentNode.getSuperTypes().forEach(sthn -> traverseHierarchyUpAndSearchForAnnotation(sthn)); + } + } + + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/SingletonAnnotationTemplateMapper.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/SingletonAnnotationTemplateMapper.java new file mode 100644 index 000000000..5b98f453a --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/SingletonAnnotationTemplateMapper.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.java.api.Annotation; +import org.springframework.sbm.java.api.Expression; + +import java.util.Map; + +public class SingletonAnnotationTemplateMapper { + private static final String DESCRIPTION_ATTRIBUTE = "description"; + private static final String NAME_ATTRIBUTE = "name"; + private static final String EJB_SINGLETON_ANNOTATAION_FQN = "javax.ejb.Singleton"; + + public String mapToServiceAnnotation(Annotation annotation) { + + if (!EJB_SINGLETON_ANNOTATAION_FQN.equals(annotation.getFullyQualifiedName())) { + throw new IllegalArgumentException("Passed invalid annotation '" + annotation.getFullyQualifiedName() + "'"); + } + + StringBuilder serviceAnnotationBuilder = new StringBuilder(); + + Map attributes = annotation.getAttributes(); + if (attributes.containsKey(DESCRIPTION_ATTRIBUTE)) { + serviceAnnotationBuilder.append("/**").append("\n"); + String descriptionValue = attributes.get(DESCRIPTION_ATTRIBUTE).getAssignmentRightSide().printVariable(); + serviceAnnotationBuilder.append("* ").append(descriptionValue).append("\n"); + serviceAnnotationBuilder.append("*/").append("\n"); + } + + serviceAnnotationBuilder.append("@Service"); + + if (attributes.containsKey(NAME_ATTRIBUTE)) { + Expression expression = attributes.get(NAME_ATTRIBUTE); + String nameAttributeValue = expression.getAssignmentRightSide().printVariable(); + serviceAnnotationBuilder.append("(\"").append(nameAttributeValue).append("\")"); + } + + return serviceAnnotationBuilder.toString(); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/StatelessAnnotationTemplateMapper.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/StatelessAnnotationTemplateMapper.java new file mode 100644 index 000000000..a05698454 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/StatelessAnnotationTemplateMapper.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.java.api.Annotation; +import org.springframework.sbm.java.api.Expression; + +import java.util.Map; + +/** + * Helps with the migration of {@code @Stateless} annotation to {@code @Service} annotation. + */ +public class StatelessAnnotationTemplateMapper { + + private static final String DESCRIPTION_ATTRIBUTE = "description"; + private static final String NAME_ATTRIBUTE = "name"; + private static final String EJB_STATELESS_ANNOTATAION_FQN = "javax.ejb.Stateless"; + + public String mapToServiceAnnotation(Annotation annotation) { + + if (!EJB_STATELESS_ANNOTATAION_FQN.equals(annotation.getFullyQualifiedName())) { + throw new IllegalArgumentException("Passed invalid annotation '" + annotation.getFullyQualifiedName() + "'"); + } + + StringBuilder serviceAnnotationBuilder = new StringBuilder(); + + Map attributes = annotation.getAttributes(); + if (attributes.containsKey(DESCRIPTION_ATTRIBUTE)) { + serviceAnnotationBuilder.append("/**").append("\n"); + String descriptionValue = attributes.get(DESCRIPTION_ATTRIBUTE).getAssignmentRightSide().printVariable(); + serviceAnnotationBuilder.append("* ").append(descriptionValue).append("\n"); + serviceAnnotationBuilder.append("*/").append("\n"); + } + + serviceAnnotationBuilder.append("@Service"); + + if (attributes.containsKey(NAME_ATTRIBUTE)) { + Expression expression = attributes.get(NAME_ATTRIBUTE); + String nameAttributeValue = expression.getAssignmentRightSide().printVariable(); + serviceAnnotationBuilder.append("(\"").append(nameAttributeValue).append("\")"); + } + + return serviceAnnotationBuilder.toString(); + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/TypeAndSourceFile.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/TypeAndSourceFile.java new file mode 100644 index 000000000..48b81851c --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/TypeAndSourceFile.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.Type; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class TypeAndSourceFile { + + private final JavaSource javaSource; + private final Type type; + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/conditions/NoTransactionalAnnotationPresentOnTypeAnnotatedWith.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/conditions/NoTransactionalAnnotationPresentOnTypeAnnotatedWith.java new file mode 100644 index 000000000..f1636b5ff --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/conditions/NoTransactionalAnnotationPresentOnTypeAnnotatedWith.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 - 2023 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.ejb.conditions; + +import org.springframework.sbm.engine.recipe.Condition; +import org.springframework.sbm.engine.context.ProjectContext; +import lombok.Getter; +import lombok.Setter; + +/** + * Condition is true if any type found annotated with {@code typeAnnotatedWith} but no {@code org.springframework.transaction.annotation.Transactional} annotation is present. + */ +@Getter +@Setter +public class NoTransactionalAnnotationPresentOnTypeAnnotatedWith implements Condition { + + private String typeAnnotatedWith; + private static final String TRANSACTION_ANNOTATION = "org.springframework.transaction.annotation.Transactional"; + + @Override + public String getDescription() { + return "Add @Transactional annotation to types annotated with " + typeAnnotatedWith + "."; + } + + @Override + public boolean evaluate(ProjectContext context) { + return context.getProjectJavaSources().asStream() + .flatMap(js -> js.getTypes().stream()) + .anyMatch(t -> t.hasAnnotation(typeAnnotatedWith) && !t.hasAnnotation(TRANSACTION_ANNOTATION)); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/recipes/MigrateEjbJarDeploymentDescriptorRecipe.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/recipes/MigrateEjbJarDeploymentDescriptorRecipe.java new file mode 100644 index 000000000..cb48c4ff8 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/recipes/MigrateEjbJarDeploymentDescriptorRecipe.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 - 2023 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.ejb.recipes; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.sbm.common.migration.conditions.FileMatchingPatternExist; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.jee.ejb.actions.MigrateEjbDeploymentDescriptor; + +@Configuration +public class MigrateEjbJarDeploymentDescriptorRecipe { + @Bean + public Recipe ejbJarDeploymentDescriptor() { + return Recipe.builder() + .name("migrate-ejb-jar-deployment-descriptor") + .order(90) + .description("Add or overrides @Stateless annotation as defined in ejb deployment descriptor") + .condition(new FileMatchingPatternExist("/**/ejb-jar.xml")) // Recipe condition is True thus it'll be applicable based on applicability of actions + .action( + MigrateEjbDeploymentDescriptor.builder() + .condition( + FileMatchingPatternExist.builder() + .pattern("/**/ejb-jar.xml") + .build() + ) + .build() + ) + .build(); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java new file mode 100644 index 000000000..724ebbe17 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java @@ -0,0 +1,159 @@ +/* + * Copyright 2021 - 2023 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; + +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaParser; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.build.migration.actions.AddDependencies; +import org.springframework.sbm.build.migration.conditions.NoExactDependencyExist; +import org.springframework.sbm.engine.recipe.OpenRewriteDeclarativeRecipeAdapter; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.engine.recipe.RewriteRecipeLoader; +import org.springframework.sbm.java.JavaRecipeAction; +import org.springframework.sbm.java.impl.ClasspathRegistry; +import org.springframework.sbm.java.migration.actions.ReplaceTypeAction; +import org.springframework.sbm.java.migration.conditions.HasAnnotation; +import org.springframework.sbm.java.migration.conditions.HasImportStartingWith; +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 java.util.List; +import java.util.function.Supplier; + +@Configuration +//@RequiredArgsConstructor +public class MigrateJaxRsRecipe { + + + private final Supplier javaParserSupplier = () -> JavaParser.fromJavaVersion().classpath(ClasspathRegistry.getInstance().getCurrentDependencies()).build(); + + @Bean + public Recipe jaxRs(RewriteRecipeLoader rewriteRecipeLoader) { + return Recipe.builder() + .name("migrate-jax-rs") + .order(60) + .description("Any class has import starting with javax.ws.rs") + .condition(HasImportStartingWith.builder().value("javax.ws.rs").description("Any class has import starting with javax.ws.rs").build()) + .actions(List.of( + + AddDependencies.builder() + .dependencies( + List.of( + Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-web").version("2.3.4.RELEASE").build() + ) + ) + .description("Add spring-boot-starter-web dependency to build file.") + .condition(NoExactDependencyExist.builder().dependency(Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-web").build()).build()) + .build(), + + ConvertJaxRsAnnotations.builder() + .condition(HasTypeAnnotation.builder().annotation("javax.ws.rs.Path").build()) + .description("Convert JAX-RS annotations into Spring Boot annotations.") + .build(), + + // Important! Replace method parameter annotations after ConvertJaxRsAnnotations action + // ConvertJaxRsAnnotations examines Jax-Rs annotations on the parameters to determine the request body param + + ReplaceTypeAction.builder() + .condition(HasAnnotation.builder().annotation("javax.ws.rs.PathParam").build()) + .description("Replace JAX-RS @PathParam with Spring Boot @PathVariable annotation.") + .existingType("javax.ws.rs.PathParam") + .withType("org.springframework.web.bind.annotation.PathVariable") + .build(), + + ReplaceTypeAction.builder() + .condition(HasAnnotation.builder().annotation("javax.ws.rs.QueryParam").build()) + .description("Replace JAX-RS @QueryParam with Spring Boot @RequestParam annotation.") + .existingType("javax.ws.rs.QueryParam") + .withType("org.springframework.web.bind.annotation.RequestParam") + .build(), + + ReplaceTypeAction.builder() + .condition(HasAnnotation.builder().annotation("javax.ws.rs.FormParam").build()) + .description("Replace JAX-RS @FormParam with Spring Boot @RequestParam annotation.") + .existingType("javax.ws.rs.QueryParam") + .withType("org.springframework.web.bind.annotation.RequestParam") + .build(), + + JavaRecipeAction.builder() + .condition(HasImportStartingWith.builder().value("javax.ws.rs.core.MediaType").build()) + .description("Replace JaxRs MediaType with it's Spring equivalent.") + .recipe(new ReplaceMediaType(javaParserSupplier)) + .build(), + + JavaRecipeAction.builder() + .condition(HasImportStartingWith.builder().value("javax.ws.rs.core.HttpHeaders").build()) + .description("Replace JaxRs HttpHeaders with it's Spring equivalent.") + .recipe(new SwapHttHeaders()) + .build(), + + JavaRecipeAction.builder() + .condition(HasImportStartingWith.builder().value("javax.ws.rs.core.MultivaluedMap").build()) + .description("Replace JaxRs MultivaluedMap with it's Spring equivalent.") + .recipe(new ChangeType("javax.ws.rs.core.MultivaluedMap", "org.springframework.util.MultiValueMap", false)) + .build(), + + JavaRecipeAction.builder() + .condition(HasImportStartingWith.builder().value("javax.ws.rs.core.CacheControl").build()) + .description("Replace JaxRs CacheControl with it's Spring equivalent.") + .recipe(new SwapCacheControl()) + .build(), + + JavaRecipeAction.builder() + .condition(HasImportStartingWith.builder().value("javax.ws.rs.core.Response").build()) + .description("Replace JaxRs Response and ResponseBuilder with it's Spring equivalent.") + .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") + .rewriteRecipeLoader(rewriteRecipeLoader) + .openRewriteRecipe( + """ + type: specs.openrewrite.org/v1beta/recipe + name: org.springframework.sbm.jee.MakeRequestParamsOptional + displayName: Set required=false for @RequestParam without 'required' + description: Set required=false for @RequestParam without 'required' + causesAnotherCycle: true + recipeList: + - org.openrewrite.java.AddOrUpdateAnnotationAttribute: + annotationType: "org.springframework.web.bind.annotation.RequestParam" + attributeName: "required" + attributeValue: "false" + addOnly: true + """) + .build() + ) + ) + .build(); + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java new file mode 100644 index 000000000..af8649909 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java @@ -0,0 +1,189 @@ +/* + * Copyright 2021 - 2023 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.actions; + +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.*; +import org.springframework.sbm.engine.context.ProjectContext; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + + +@NoArgsConstructor +@SuperBuilder +public class ConvertJaxRsAnnotations extends AbstractAction { + + public static final Pattern JAXRS_ANNOTATION_PATTERN = Pattern.compile("javax\\.ws\\.rs\\..*"); + public static final Pattern SPRING_ANNOTATION_PATTERN = Pattern.compile("org\\.springframework\\.web\\.bind\\..*"); + + @Override + public void apply(ProjectContext context) { + for (JavaSource js : context.getProjectJavaSources().list()) { + for (Type t : js.getTypes()) { + if (t.hasAnnotation("javax.ws.rs.Path")) { + transform(t); + } + } + } + } + + private void transform(Type type) { + transformTypeAnnotations(type); + transformMethodAnnotations(type); + } + + private void transformMethodAnnotations(Type type) { + type.getMethods().stream() + .filter(this::isJaxRsMethod) + .forEach(this::convertJaxRsMethodToSpringMvc); + } + + void convertJaxRsMethodToSpringMvc(Method method) { + Map attrs = new LinkedHashMap<>(); + Set methods = new LinkedHashSet<>(); + var annotations = method.getAnnotations(); + + // Add @RequestBody over the first non-annotated parameter without other jax-rs annotations + method.getParams().stream() + .filter(p -> !p.containsAnnotation(JAXRS_ANNOTATION_PATTERN)) + .filter(p -> !p.containsAnnotation(SPRING_ANNOTATION_PATTERN)) + .findFirst() + .ifPresent(p -> p.addAnnotation("@RequestBody", "org.springframework.web.bind.annotation.RequestBody")); + + for (Annotation a : annotations) { + if (a == null) { + continue; + } + String fullyQualifiedName = a.getFullyQualifiedName(); + if (fullyQualifiedName != null) { + switch (fullyQualifiedName) { + case "javax.ws.rs.Path": + attrs.put("value", a.getAttribute("value")); + method.removeAnnotation(a); + break; + case "javax.ws.rs.Consumes": + attrs.put("consumes", a.getAttribute("value")); + method.removeAnnotation(a); + break; + case "javax.ws.rs.Produces": + attrs.put("produces", a.getAttribute("value")); + method.removeAnnotation(a); + break; + case "javax.ws.rs.POST": + methods.add("POST"); + method.removeAnnotation(a); + break; + case "javax.ws.rs.GET": + methods.add("GET"); + method.removeAnnotation(a); + break; + case "javax.ws.rs.PUT": + methods.add("PUT"); + method.removeAnnotation(a); + break; + case "javax.ws.rs.DELETE": + methods.add("DELETE"); + method.removeAnnotation(a); + break; + case "javax.ws.rs.HEAD": + methods.add("HEAD"); + method.removeAnnotation(a); + break; + case "javax.ws.rs.PATCH": + methods.add("PATCH"); + method.removeAnnotation(a); + break; + case "javax.ws.rs.TRACE": + methods.add("TRACE"); + method.removeAnnotation(a); + break; + default: + } + } + } + + if (method.getAnnotations().size() < annotations.size()) { + StringBuilder sb = new StringBuilder("@RequestMapping"); + boolean parametersPresent = !(attrs.isEmpty() && methods.isEmpty()); + if (parametersPresent) { + sb.append("("); + } + + sb.append(attrs.entrySet().stream() + .map(e -> e.getKey() + " = " + e.getValue().print()) + .collect(Collectors.joining(", "))); + + if (!methods.isEmpty()) { + if(!attrs.entrySet().isEmpty()) { + sb.append(", "); + } + if (methods.size() == 1) { + sb.append("method = RequestMethod." + methods.iterator().next()); + } else { + sb.append("method = {"); + sb.append(methods.stream().map(m -> "RequestMethod." + m).collect(Collectors.joining(", "))); + sb.append("}"); + } + } + if (parametersPresent) { + sb.append(")"); + } + + method.addAnnotation(sb.toString(), "org.springframework.web.bind.annotation.RequestMapping", "org.springframework.web.bind.annotation.RequestMethod"); + + } + } + + private boolean isJaxRsMethod(Method method) { + return method.containsAnnotation(JAXRS_ANNOTATION_PATTERN); + } + + private void transformTypeAnnotations(Type type) { + List annotations = type.getAnnotations(); + Optional found = annotations.stream().filter(a -> "javax.ws.rs.Path".equals(a.getFullyQualifiedName())).findFirst(); + if (found.isPresent()) { + type.removeAnnotation(found.get()); + type.addAnnotation("org.springframework.web.bind.annotation.RestController"); + Map attributes = new LinkedHashMap<>(found.get().getAttributes()); + for (Annotation a : annotations) { + if (a == null) { + continue; + } + String fullyQualifiedName = a.getFullyQualifiedName(); + if (fullyQualifiedName != null) { + switch (fullyQualifiedName) { + case "javax.ws.rs.Consumes" -> { + attributes.put("consumes", a.getAttribute("value")); + type.removeAnnotation(a); + } + case "javax.ws.rs.Produces" -> { + attributes.put("produces", a.getAttribute("value")); + type.removeAnnotation(a); + } + default -> { + } + } + } + } + String rmAttrs = attributes.entrySet().stream().map(e -> e.getKey() + " = " + e.getValue().print()).collect(Collectors.joining(", ")); + type.addAnnotation("@RequestMapping(" + rmAttrs + ")", "org.springframework.web.bind.annotation.RequestMapping"); + } + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java new file mode 100644 index 000000000..06dae3513 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021 - 2023 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/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java new file mode 100644 index 000000000..0ec2b9586 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021 - 2023 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/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.java new file mode 100644 index 000000000..410ee56d1 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.java @@ -0,0 +1,176 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.Recipe; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; +import org.springframework.sbm.java.migration.recipes.RewriteConstructorInvocation; +import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; +import org.springframework.sbm.java.migration.recipes.openrewrite.ReplaceConstantWithAnotherConstant; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import static org.springframework.sbm.java.migration.recipes.RewriteConstructorInvocation.constructorMatcher; + +public class ReplaceMediaType extends Recipe { + + public ReplaceMediaType(Supplier javaParserSupplier) { + + // Constants + Map mappings = new HashMap<>(); + mappings.put("APPLICATION_ATOM_XML", "APPLICATION_ATOM_XML_VALUE"); + mappings.put("APPLICATION_ATOM_XML_TYPE", "APPLICATION_ATOM_XML"); + + mappings.put("APPLICATION_FORM_URLENCODED", "APPLICATION_FORM_URLENCODED_VALUE"); + mappings.put("APPLICATION_FORM_URLENCODED_TYPE", "APPLICATION_FORM_URLENCODED"); + + mappings.put("APPLICATION_JSON", "APPLICATION_JSON_VALUE"); + mappings.put("APPLICATION_JSON_TYPE", "APPLICATION_JSON"); + + mappings.put("APPLICATION_JSON_PATCH_JSON", "APPLICATION_JSON_PATCH_JSON_VALUE"); + mappings.put("APPLICATION_JSON_PATCH_JSON_TYPE", "APPLICATION_JSON_PATCH_JSON"); + + mappings.put("APPLICATION_OCTET_STREAM", "APPLICATION_OCTET_STREAM_VALUE"); + mappings.put("APPLICATION_OCTET_STREAM_TYPE", "APPLICATION_OCTET_STREAM"); + + mappings.put("APPLICATION_SVG_XML", "APPLICATION_SVG_XML_VALUE"); + mappings.put("APPLICATION_SVG_XML_TYPE", "APPLICATION_SVG_XML"); + + mappings.put("APPLICATION_XHTML_XML", "APPLICATION_XHTML_XML_VALUE"); + mappings.put("APPLICATION_XHTML_XML_TYPE", "APPLICATION_XHTML_XML"); + + mappings.put("APPLICATION_XML", "APPLICATION_XML_VALUE"); + mappings.put("APPLICATION_XML_TYPE", "APPLICATION_XML"); + + mappings.put("MULTIPART_FORM_DATA", "MULTIPART_FORM_DATA_VALUE"); + mappings.put("MULTIPART_FORM_DATA_TYPE", "MULTIPART_FORM_DATA"); + + mappings.put("SERVER_SENT_EVENTS", "TEXT_EVENT_STREAM_VALUE"); + mappings.put("SERVER_SENT_EVENTS_TYPE", "TEXT_EVENT_STREAM"); + + mappings.put("TEXT_HTML", "TEXT_HTML_VALUE"); + mappings.put("TEXT_HTML_TYPE", "TEXT_HTML"); + + mappings.put("TEXT_PLAIN", "TEXT_PLAIN_VALUE"); + mappings.put("TEXT_PLAIN_TYPE", "TEXT_PLAIN"); + + mappings.put("TEXT_XML", "TEXT_XML_VALUE"); + mappings.put("TEXT_XML_TYPE", "TEXT_XML"); + + mappings.put("WILDCARD", "ALL_VALUE"); + mappings.put("WILDCARD_TYPE", "ALL"); + + mappings.forEach( + (key, value) -> doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType." + key,"org.springframework.http.MediaType." + value)) + ); + + doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType.CHARSET_PARAMETER","org.springframework.util.MimeType.PARAM_CHARSET")); + doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType.MEDIA_TYPE_WILDCARD","org.springframework.util.MimeType.WILDCARD_TYPE")); + + // instance methods + // #isCompatible(MediaType) + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.MediaType isCompatible(javax.ws.rs.core.MediaType)"), (v, m, addImport) -> { + JavaType type = JavaType.buildType("org.springframework.http.MediaType"); + + J.Identifier newMethodName = m.getName().withSimpleName("isCompatibleWith"); + Expression newSelect = m.getSelect().withType(type); + JavaType.Method newMethodType = m.getMethodType().withReturnType(type).withDeclaringType(TypeUtils.asFullyQualified(type)); + List newMethodArguments = List.of(m.getArguments().get(0).withType(type)); + + return m + .withName(newMethodName) + .withSelect(newSelect) + .withMethodType(newMethodType) + .withArguments(newMethodArguments); + })); + + // #withCharset(String) + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.MediaType withCharset(java.lang.String)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new MediaType(#{any(org.springframework.http.MediaType)}, Charset.forName(#{any(java.lang.String)}))") + .imports("org.springframework.http.MediaType", "java.nio.charset.Charset") + .build(); + addImport.accept("java.nio.charset.Charset"); + addImport.accept("org.springframework.http.MediaType"); + + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + })); + + // #getParameters() - comes with org.springframework.util.MimeType#getParameters() + // #getSubtype() - comes with org.springframework.util.MimeType#getSubtype() + // #getType() - comes with org.springframework.util.MimeType#getType() + // #isWildcardSubtype() - comes with org.springframework.util.MimeType#isWildcardSubtype() + // #isWildcardType() - comes with org.springframework.util.MimeType#isWildcardType() + + // static methods + + // #valueOf(String) present on Spring MediaType + + // constructors + + // MediaType() -> new MediaType(MimeType.WILDCARD_TYPE, MimeType.WILDCARD_TYPE) + doNext(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new MediaType(MimeType.WILDCARD_TYPE, MimeType.WILDCARD_TYPE)") + .imports("org.springframework.http.MediaType", "org.springframework.util.MimeType") + .build(); + addImport.accept("org.springframework.util.MimeType"); + addImport.accept("org.springframework.http.MediaType"); + + return m.withTemplate(template, m.getCoordinates().replace()); + })); + + // MediaType(String, String) - present on Spring MediaType + doNext(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String"), (v, m, addImport) -> { + JavaType type = JavaType.buildType("org.springframework.http.MediaType"); + return m.withConstructorType(m.getConstructorType().withDeclaringType(TypeUtils.asFullyQualified(type))); + })); + + // MediaType(String, String, String) -> MediaType(String, String, Charset) + doNext(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String", "java.lang.String"), (v, m, addImport) -> { + List arguments = m.getArguments(); + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new MediaType(#{any(java.lang.String)}, #{any(java.lang.String)}, Charset.forName(#{any(java.lang.String)}))") + .imports("org.springframework.http.MediaType", "java.nio.charset.Charset") + .build(); + addImport.accept("java.nio.charset.Charset"); + addImport.accept("org.springframework.http.MediaType"); + + return m.withTemplate(template, m.getCoordinates().replace(), arguments.get(0), arguments.get(1), arguments.get(2)); + })); + + // MediaType(String, String, Map) - present on Spring MediaType + doNext(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String", "java.util.Map"), (v, m, addImport) -> { + JavaType type = JavaType.buildType("org.springframework.http.MediaType"); + return m.withConstructorType(m.getConstructorType().withDeclaringType(TypeUtils.asFullyQualified(type))); + })); + + // Type references + doNext(new ChangeType("javax.ws.rs.core.MediaType", "org.springframework.http.MediaType", false)); + } + + @Override + public String getDisplayName() { + return "Replace JAX-RS MediaType with Spring MediaType"; + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java new file mode 100644 index 000000000..4441adb67 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021 - 2023 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/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java new file mode 100644 index 000000000..1a04aec6a --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java @@ -0,0 +1,248 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.Recipe; +import org.openrewrite.Tree; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaTemplate.Builder; +import org.openrewrite.java.MethodMatcher; +import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; +import org.springframework.sbm.java.migration.visitor.VisitorUtils; +import org.springframework.sbm.java.migration.visitor.VisitorUtils.AdjustTypesFromExpressionMarkers; +import org.springframework.sbm.java.migration.visitor.VisitorUtils.MarkReturnType; +import org.springframework.sbm.java.migration.visitor.VisitorUtils.MarkWithTemplate; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation.methodInvocationMatcher; +import static org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation.renameMethodInvocation; + +public class ReplaceResponseEntityBuilder extends Recipe { + + public ReplaceResponseEntityBuilder() { + // #allow(String...) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder allow(java.lang.String...)"), + (v, m, addImport) -> { + String transformedArgs = m.getArguments().stream().map(arg -> "HttpMethod.resolve(#{any()})").collect(Collectors.joining(", ")); + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.allow(" + transformedArgs + ")").imports("org.springframework.http.HttpMethod", "org.springframework.http.ResponseEntity.HeadersBuilder").build(); +// v.maybeAddImport("org.springframework.http.HttpMethod"); + addImport.accept("org.springframework.http.HttpMethod"); + List parameters = new ArrayList(); + parameters.add(m.getSelect()); + parameters.addAll(m.getArguments()); + return m.withTemplate(t, m.getCoordinates().replace(), parameters.toArray()); + } + ) + ); + + // #allow(Set) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder allow(java.util.Set)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder( + () -> v.getCursor(), + "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.allow(#{any()}.stream().map(HttpMethod::resolve).toArray(String[]::new))").imports("org.springframework.http.HttpMethod", "org.springframework.http.ResponseEntity.HeadersBuilder" + ) + .build(); +// v.maybeAddImport("org.springframework.http.HttpMethod"); + addImport.accept("org.springframework.http.HttpMethod"); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #cacheControl(CacheControl) + + // #encoding(String) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder encoding(java.lang.String)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder( + () -> v.getCursor(), + "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.header(HttpHeaders.CONTENT_ENCODING, #{any()})" + ) + .imports("org.springframework.http.HttpHeaders", "org.springframework.http.ResponseEntity.HeadersBuilder") + .build(); + addImport.accept("org.springframework.http.HttpHeaders"); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments()); + } + ) + ); + + // #contentLocation(URI) + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder contentLocation(java.net.URI)"), "location", "org.springframework.http.ResponseEntity.HeadersBuilder")); + + // #tag(String) + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder tag(java.lang.String)"), "eTag", "org.springframework.http.ResponseEntity.HeadersBuilder")); + + // #entity(Object) + // #entity(Object, Annotation[]) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder entity(java.lang.Object, ..)"), + (v, m, addImport) -> { + VisitorUtils.markWrappingInvocationWithTemplate(v, m, new MethodMatcher("javax.ws.rs.core.Response.ResponseBuilder build()"), m.getArguments().get(0).print(), this); + return m.withTemplate(JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}").build(), m.getCoordinates().replace(), m.getSelect()); + } + ) + ); + + // #expires(Date) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder expires(java.util.Date)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.setExpires(#{any()}.toInstant()))") + .build(); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #language(String) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder language(java.lang.String)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.set(HttpHeaders.CONTENT_LANGUAGE, #{any()}))") + .imports("org.springframework.http.HttpHeaders") + .build(); + addImport.accept("org.springframework.http.HttpHeaders"); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #language(Locale) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder language(java.util.Locale)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.setContentLanguage(#{any()}))") + .build(); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #lastModified(Date) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder lastModified(java.util.Date)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.lastModified(#{any()}.toInstant())") + .build(); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #location(URI) - present on Spring ResponseEntity builder classes, nothing to do + + // replaceAll(MultivaluedMap) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder replaceAll(javax.ws.rs.core.MultivaluedMap)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> {\n" + + "h.clear();\n" + + "h.addAll(#{any()});\n" + + "})") + .imports("org.springframework.util.MultiValueMap", "org.springframework.http.ResponseEntity.HeadersBuilder") + .build(); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #type(String) + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder type(java.lang.String)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.set(HttpHeaders.CONTENT_TYPE, #{any()}))") + .imports("org.springframework.http.HttpHeaders", "org.springframework.http.ResponseEntity.HeadersBuilder") + .build(); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #type(MediaType) + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder type(javax.ws.rs.core.MediaType)"), "contentType", "org.springframework.http.ResponseEntity.HeadersBuilder")); + + // #build() + // FIXME: org.springframework.http.ResponseEntity.build() does not exist. Invalid: ResponseEntity r = ResponseEntity.ok("...").build(); + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder build()"), + (v, m, addImport) -> { + MarkWithTemplate marker = m.getMarkers().findFirst(MarkWithTemplate.class).orElse(null); + if (marker != null) { + m = VisitorUtils.removeMarker(m, marker); + Builder t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.body(#{})"); + m = m.withTemplate(t.build(), m.getCoordinates().replace(), m.getSelect(), marker.getTemplate()); + } + return m.withMarkers(m.getMarkers().computeByType(new MarkReturnType(Tree.randomId(), this, "ResponseEntity", "org.springframework.http.ResponseEntity"), (o1, o2) -> o2)); + } + ) + ); + + /* + * NOT SUPPORTED: + * - cookie(NewCookie) + * - link(String, String) + * - link(URI, String) + * - links(Link...) + * - status(int) + * - status(int, String) + * - status(Status) + * - status(StatusType) + * - tag(EntityTag) + * - variant(Variant) + * - variants(List) + * - variants(Variant...) + */ + + + // Always should be the last for method call migration + // Take care of #body(Object) calls that were marked and should go at the end of method invocation chain + doNext(new RewriteMethodInvocation( + m -> m.getMarkers().findFirst(MarkWithTemplate.class).isPresent(), + (v, m, addImport) -> { + MarkWithTemplate marker = m.getMarkers().findFirst(MarkWithTemplate.class).orElse(null); + m = VisitorUtils.removeMarker(m, marker); + return m + .withTemplate( + JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.body(#{}").build(), + m.getCoordinates().replace(), + m, + marker.getTemplate()) + .withMarkers(m.getMarkers().computeByType(new MarkReturnType(Tree.randomId(), this, "ResponseEntity", "org.springframework.http.ResponseEntity"), (o1, o2) -> o2)); + } + ) + ); + + doNext(new AdjustTypesFromExpressionMarkers()); + + // Finally replace type with BodyBuilder if nothing else replaced it previously + doNext(new ChangeType("javax.ws.rs.core.Response$ResponseBuilder", "org.springframework.http.ResponseEntity$BodyBuilder", true)); + + } + + @Override + public String getDisplayName() { + return "Replace references to JAX-RS ReplaceResponseEntityBuilder"; + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.java new file mode 100644 index 000000000..e4eb8116e --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.Recipe; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaTemplate; +import org.springframework.sbm.java.migration.recipes.RewriteConstructorInvocation; +import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; + +public class SwapCacheControl extends Recipe { + + public SwapCacheControl() { + + /* + * NOT SUPPORTED: + * - valueOf(String) + * - getCacheExtension() + * - getMaxAge() + * - getNoCacheFields() + * - getPrivateFields() + * - getSMaxAge() + * - isMustRevalidate() + * - isNoCache() + * - isNoStore() + * - isNoTransform() + * - isPrivate() + * - isProxyRevalidate() + * - setMaxAge(int) + * - setMustRevalidate(boolean) + * - setNoCache(boolean) + * - setNoStore(boolean) + * - setNoTransform(boolean) + * - setPrivate(boolean) + * - setProxyRevalidate(boolean) + */ + + // setSMaxAge(int) + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.CacheControl setSMaxAge(int)"), (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.CacheControl)}.sMaxAge(#{any(int)}, TimeUnit.SECONDS)") + .imports("java.util.concurrent.TimeUnit", "org.springframework.http.CacheControl") + .build(); + addImport.accept("java.util.concurrent.TimeUnit"); + return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + })); + + // constructor + doNext(new RewriteConstructorInvocation(RewriteConstructorInvocation.constructorMatcher("javax.ws.rs.core.CacheControl"), (v, c, addImport) -> { + JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "CacheControl.empty()") + .imports("org.springframework.http.CacheControl") + .build(); + addImport.accept("org.springframework.http.CacheControl"); + v.maybeRemoveImport("javax.ws.rs.core.CacheControl"); + return c.withTemplate(t, c.getCoordinates().replace()); + })); + + doNext(new ChangeType("javax.ws.rs.core.CacheControl", "org.springframework.http.CacheControl", false)); + } + + @Override + public String getDisplayName() { + return "Swap JAX-RS CacheControl with Spring CacheControl"; + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java new file mode 100644 index 000000000..fab2b8309 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.Recipe; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaTemplate; +import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; +import org.springframework.sbm.java.migration.recipes.openrewrite.ReplaceConstantWithAnotherConstant; + +import java.util.HashMap; +import java.util.Map; + +public class SwapFamilyForSeries extends Recipe { + + public SwapFamilyForSeries() { + Map fieldsMapping = new HashMap<>(); + fieldsMapping.put("INFORMATIONAL", "INFORMATIONAL"); + fieldsMapping.put("SUCCESSFUL", "SUCCESSFUL"); + fieldsMapping.put("REDIRECTION", "REDIRECTION"); + fieldsMapping.put("CLIENT_ERROR", "CLIENT_ERROR"); + fieldsMapping.put("SERVER_ERROR", "SERVER_ERROR"); + fieldsMapping.forEach( + (key, value) -> doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.Response.Status.Family." + key,"org.springframework.http.HttpStatus.Series." + value)) + ); + + // All constants seem to match on both types - let ChangeType take care of type changing for field accesses + doNext(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.Status.Family familyOf(int)"), + (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "HttpStatus.Series.resolve(#{any(int)})").build(); + v.maybeAddImport("org.springframework.http.HttpStatus.Series"); + addImport.accept("org.springframework.http.HttpStatus"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + } + ) + ); + + doNext(new ChangeType("javax.ws.rs.core.Response$Status$Family", "org.springframework.http.HttpStatus$Series", true)); + + } + + @Override + public String getDisplayName() { + return "Swap JAX-RS Family with Spring HttpStatus.Series"; + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java new file mode 100644 index 000000000..9e0f2b94b --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java @@ -0,0 +1,141 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.Recipe; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.tree.J.NewClass; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.TypeUtils; +import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; + +import java.util.List; + +import static org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation.methodInvocationMatcher; +import static org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation.renameMethodInvocation; + +public class SwapHttHeaders extends Recipe { + + public SwapHttHeaders() { + /* + * NOT SUPPORTED: + * - CONTENT_ID (possibly replace with "Content-ID") + * - LAST_EVENT_ID_HEADER (possibly replace with "Last-Event-ID" + * - getAcceptableMediaTypes() + * - getCookies() + */ + // #getAcceptableLanguages() + doNext(new RewriteMethodInvocation( + methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getAcceptableLanguages()"), + (v, m, addImport) -> { + JavaType javaType = JavaType.buildType("org.springframework.http.HttpHeaders"); + return m + .withSelect(m.getSelect().withType(javaType)) + .withName(m.getName().withSimpleName("getAcceptLanguageAsLocales")) + .withMethodType(m.getMethodType().withReturnType(JavaType.buildType("java.util.List")).withDeclaringType(((JavaType.ShallowClass) javaType).getOwningClass())); + } + ) + ); + + // #getDate() + doNext(new RewriteMethodInvocation( + methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getDate()"), + (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new Date()") + .imports("java.util.Date") + .build(); + addImport.accept("java.util.Date"); + NewClass newMethod = (NewClass) m.withTemplate(template, m.getCoordinates().replace()); + JavaType javaType = JavaType.buildType("org.springframework.http.HttpHeaders"); + return newMethod.withArguments(List.of( + m.withSelect(m.getSelect().withType(javaType)) + .withMethodType(m.getMethodType().withDeclaringType(TypeUtils.asFullyQualified(javaType))) + .withPrefix(Space.EMPTY) + )); + } + ) + ); + + // #getHeaderString(String) + doNext(new RewriteMethodInvocation( + methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getHeaderString(java.lang.String)"), + (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "String.join(\", \", #{any(org.springframework.http.HttpHeaders)}.get(#{any(java.lang.String)})") + .build(); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #getLanguage() + doNext(new RewriteMethodInvocation( + methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getLanguage()"), + (v, m, addImport) -> { + JavaType javaType = JavaType.buildType("org.springframework.http.HttpHeaders"); + return m + .withName(m.getName().withSimpleName("getContentLanguage")) + .withSelect(m.getSelect().withType(javaType)) + .withMethodType(m.getMethodType().withDeclaringType(TypeUtils.asFullyQualified(JavaType.buildType("org.springframework.http.HttpHeaders")))); + } + ) + ); + + // #getLength() + doNext(new RewriteMethodInvocation( + methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getLength()"), + (v, m, addImport) -> { + JavaType javaType = JavaType.buildType("org.springframework.http.HttpHeaders"); + return m + .withName(m.getName().withSimpleName("getContentLength")) + .withSelect(m.getSelect().withType(javaType)) + .withMethodType(m.getMethodType().withDeclaringType(TypeUtils.asFullyQualified(JavaType.buildType("org.springframework.http.HttpHeaders")))); + } + ) + ); + + // #getMediaType() + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getMediaType()"), "getContentType", "org.springframework.http.HttpHeaders")); + + // #getRequestHeader(String) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getRequestHeader(java.lang.String)"), + (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.HttpHeaders)}.get(#{any(java.lang.String)})") + .build(); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #getRequestHeaders() + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getRequestHeaders()"), + (v, m, addImport) -> { + // Spring HttpHeaders is a MultiValueMap, hence just leave the expression and remove the call + return m.withTemplate(JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.HttpHeaders)}").build(), m.getCoordinates().replace(), m.getSelect()); + } + ) + ); + + doNext(new ChangeType("javax.ws.rs.core.HttpHeaders", "org.springframework.http.HttpHeaders", false)); + } + + @Override + public String getDisplayName() { + return "Swap JAX-RS HttpHeaders with Spring HttpHeaders"; + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java new file mode 100644 index 000000000..33ade0d0b --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java @@ -0,0 +1,376 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Tree; +import org.openrewrite.java.*; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.MethodInvocation; +import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; +import org.springframework.sbm.java.migration.visitor.VisitorUtils; +import org.springframework.sbm.search.recipe.CommentJavaSearchResult; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation.methodInvocationMatcher; +import static org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation.renameMethodInvocation; + +public class SwapResponseWithResponseEntity extends Recipe { + + public SwapResponseWithResponseEntity(Supplier javaParserSupplier) { + + doNext(new SwapStatusForHttpStatus(javaParserSupplier)); + // #status(int) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(int)"), (v, m, addImport) -> { + String args = m.getArguments().stream().map(a -> "#{any()}").collect(Collectors.joining(", ")); + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(" + args + ")") + .imports("org.springframework.http.ResponseEntity") + .build(); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + addImport.accept("org.springframework.http.ResponseEntity"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().toArray()); + })); + + // #status(int, String) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(int, java.lang.String)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(#{any()})") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(int, java.lang.String) - dropped java.lang.String argument"))); + })); + + // #status(Status) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(javax.ws.rs.core.Response.Status)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(#{any()})") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(javax.ws.rs.core.Response.StatusType) - replaced with status(int)"))); + })); + + // #status(StatusType) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(javax.ws.rs.core.Response.StatusType)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(#{()})") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(javax.ws.rs.core.Response.StatusType) - replaced with status(int)"))); + })); + + // #ok() + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.ok()") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace()); + })); + + // #ok(Object) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object)"), (v, m, addImport) -> { + List args = m.getArguments(); + if(J.Literal.class.isInstance(m.getArguments().get(0))) { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.ok()") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + m = m.withTemplate(template, m.getCoordinates().replace()); + markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); + } + return m; + })); + + // #ok(Object, MediaType) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object, javax.ws.rs.core.MediaType)"), (v, m, addImport) -> { + List args = m.getArguments(); + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.ok().contentType(#{any()})") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); + return m.withTemplate(template, m.getCoordinates().replace(), args.get(1)); + })); + + // #ok(Object, String) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object, java.lang.String)"), (v, m, addImport) -> { + List args = m.getArguments(); + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.ok().contentType(MediaType.parseMediaType(#{any()}))") + .imports("org.springframework.http.ResponseEntity", "org.springframework.http.MediaType") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + addImport.accept("org.springframework.http.MediaType"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); + return m.withTemplate(template, m.getCoordinates().replace(), args.get(1)); + })); + + // #accepted() + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response accepted()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.accepted()") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace()); + })); + + // #accepted(Object) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response accepted(java.lang.Object)"), (v, m, addImport) -> { + List args = m.getArguments(); + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.accepted()") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + m = m.withTemplate(template, m.getCoordinates().replace()); + markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); + return m; + })); + + // #created(URI) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response created(java.net.URI)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.created(#{any()})") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + })); + + // #fromResponse(Response) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response fromResponse(javax.ws.rs.core.Response)"), (v, m, addImport) -> { + Expression e = m.getArguments().get(0); + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(#{any()}.getStatusCode()).headers(#{any()}.getHeaders())") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + markTopLevelInvocationWithTemplate(v, m, e.print() + ".getBody()"); + return m.withTemplate(template, m.getCoordinates().replace(), e, e); + })); + + // #noContent() + // TODO: returns HeadersBuilder + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response noContent()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.noContent()") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace()); + })); + + // #notAcceptable(List { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.NOT_MODIFIED)") + .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace()); + })); + + // notModified(String) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response notModified(java.lang.String)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.NOT_MODIFIED).eTag(#{any()})") + .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + addImport.accept("org.springframework.http.HttpStatus"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + })); + + // notModified(EntityTag) - migration not supported + + // #seeOther(URI) + // TODO: Returns BodyBuilder + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response seeOther(java.net.URI)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.SEE_OTHER).location(#{any()})") + .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + addImport.accept("org.springframework.http.HttpStatus"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + })); + + // #serverError() + // Returns BodyBuilder + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response serverError()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.SERVER_ERROR)") + .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + addImport.accept("org.springframework.http.HttpStatus"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace()); + })); + + // #temporaryRedirect(URI) + // TODO: Returns BodyBuilder + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response temporaryRedirect(java.net.URI)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(#{any()})") + .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + addImport.accept("org.springframework.http.HttpStatus"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + })); + + // INSTANCE METHODS + + // #getAllowedMethods() + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getAllowedMethods()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getAllow().stream().map(m -> m.toString()).collect(Collectors.toList())") + .imports("java.util.stream.Collectors", "org.springframework.http.ResponseEntity") + .build(); + addImport.accept("java.util.stream.Collectors"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getDate() + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getDate()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new Date(#{any(org.springframework.http.ResponseEntity)}.getHeaders().getDate())") + .imports("java.util.Date", "org.springframework.http.ResponseEntity") + .build(); + addImport.accept("java.util.Date"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getEntity() + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getEntity()"), "getBody", "org.springframework.http.ResponseEntity")); + + // #getEntityTag() + // TODO: return type not EntityTag but String after migration + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getEntityTag()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getETag()") + .build(); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getHeaderString(String) + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getHeaderString(java.lang.String)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().get(#{any()}).stream().collect(Collectors.joining(\", \"))") + .imports("java.util.stream.Collectors") + .build(); + v.maybeAddImport("java.util.stream.Collectors"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + })); + + // #getMetadata() + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getMetadata()"), "getHeaders", "org.springframework.http.ResponseEntity")); + + // #getLanguage() + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLanguage()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentLanguage()") + .build(); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getLastModified() + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLastModified()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new Date(#{any(org.springframework.http.ResponseEntity)}.getHeaders().getLastModified())") + .imports("java.util.Date") + .build(); + addImport.accept("java.util.Date"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getLength() + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLength()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentLength()") + .build(); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getLocation() + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLocation()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getLocation()") + .build(); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getMediaType() + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getMediaType()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentType()") + .build(); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getStatus() + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStatus()"), "getStatusCodeValue", "org.springframework.http.ResponseEntity")); + + // #getStatusInfo() + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStatusInfo()"), "getStatusCode", "org.springframework.http.ResponseEntity")); + + // #getStringHeaders() + // TODO: different return type + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStringHeaders()"), "getHeaders", "org.springframework.http.ResponseEntity")); + + // #hasEntity() + doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response hasEntity()"), "hasBody", "org.springframework.http.ResponseEntity")); + + // #readEntity(..) + doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response readEntity(..)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getBody())") + .build(); + v.maybeRemoveImport("java.lang.annotation.Annotation"); + v.maybeRemoveImport("javax.ws.rs.core.GenericType"); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // #getHeaders() - present on ResponseEntity but different return type. Nothing can be done about it for now... + + // #getCookies() - not implemented + // #bufferEntity() - not implemented + // #close() - not implemented + // #getLink(String) - not implemented + // #getLinkBuilder(String) - not implemented + // #getLinks() - not implemented + // #hasLink() - not implemented + + + doNext(new ReplaceResponseEntityBuilder()); + + doNext(new ChangeType("javax.ws.rs.core.Response", "org.springframework.http.ResponseEntity", false)); + } + + private void markTopLevelInvocationWithTemplate(JavaVisitor v, MethodInvocation m, String template) { + VisitorUtils.markWrappingInvocationWithTemplate(v, m, new MethodMatcher("javax.ws.rs.core.Response.ResponseBuilder build()"), template, this); + } + + @Override + public String getDisplayName() { + return "Replace JAX-RS Response with Spring ResponseEntity"; + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java new file mode 100644 index 000000000..3de007445 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java @@ -0,0 +1,117 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.Recipe; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; +import org.springframework.sbm.java.migration.recipes.openrewrite.ReplaceConstantWithAnotherConstant; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +import static org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation.methodInvocationMatcher; + +public class SwapStatusForHttpStatus extends Recipe { + + public SwapStatusForHttpStatus(Supplier javaParserSupplier) { + // Switch JAX-RS Family to Spring HttpStatus.Series + doNext(new SwapFamilyForSeries()); + + Map fieldsMapping = new HashMap<>(); + fieldsMapping.put("OK", "OK"); + fieldsMapping.put("ACCEPTED", "ACCEPTED"); + fieldsMapping.put("BAD_GATEWAY", "BAD_GATEWAY"); + fieldsMapping.put("BAD_REQUEST", "BAD_REQUEST"); + fieldsMapping.put("CONFLICT", "CONFLICT"); + fieldsMapping.put("CREATED", "CREATED"); + fieldsMapping.put("EXPECTATION_FAILED", "EXPECTATION_FAILED"); + fieldsMapping.put("FORBIDDEN", "FORBIDDEN"); + fieldsMapping.put("FOUND", "FOUND"); + fieldsMapping.put("GATEWAY_TIMEOUT", "GATEWAY_TIMEOUT"); + fieldsMapping.put("GONE", "GONE"); + fieldsMapping.put("HTTP_VERSION_NOT_SUPPORTED", "HTTP_VERSION_NOT_SUPPORTED"); + fieldsMapping.put("INTERNAL_SERVER_ERROR", "INTERNAL_SERVER_ERROR"); + fieldsMapping.put("LENGTH_REQUIRED", "LENGTH_REQUIRED"); + fieldsMapping.put("METHOD_NOT_ALLOWED", "METHOD_NOT_ALLOWED"); + fieldsMapping.put("MOVED_PERMANENTLY", "MOVED_PERMANENTLY"); + fieldsMapping.put("NETWORK_AUTHENTICATION_REQUIRED", "NETWORK_AUTHENTICATION_REQUIRED"); + fieldsMapping.put("NO_CONTENT", "NO_CONTENT"); + fieldsMapping.put("NOT_ACCEPTABLE", "NOT_ACCEPTABLE"); + fieldsMapping.put("NOT_FOUND", "NOT_FOUND"); + fieldsMapping.put("NOT_IMPLEMENTED", "NOT_IMPLEMENTED"); + fieldsMapping.put("NOT_MODIFIED", "NOT_MODIFIED"); + fieldsMapping.put("PARTIAL_CONTENT", "PARTIAL_CONTENT"); + fieldsMapping.put("PAYMENT_REQUIRED", "PAYMENT_REQUIRED"); + fieldsMapping.put("PRECONDITION_FAILED", "PRECONDITION_FAILED"); + fieldsMapping.put("PRECONDITION_REQUIRED", "PRECONDITION_REQUIRED"); + fieldsMapping.put("PROXY_AUTHENTICATION_REQUIRED", "PROXY_AUTHENTICATION_REQUIRED"); + + // Different !!! + fieldsMapping.put("REQUEST_ENTITY_TOO_LARGE", "PAYLOAD_TOO_LARGE"); + + fieldsMapping.put("REQUEST_HEADER_FIELDS_TOO_LARGE", "REQUEST_HEADER_FIELDS_TOO_LARGE"); + fieldsMapping.put("REQUEST_TIMEOUT", "REQUEST_TIMEOUT"); + fieldsMapping.put("REQUEST_URI_TOO_LONG", "REQUEST_URI_TOO_LONG"); + fieldsMapping.put("REQUESTED_RANGE_NOT_SATISFIABLE", "REQUESTED_RANGE_NOT_SATISFIABLE"); + fieldsMapping.put("RESET_CONTENT", "RESET_CONTENT"); + fieldsMapping.put("SEE_OTHER", "SEE_OTHER"); + fieldsMapping.put("SERVICE_UNAVAILABLE", "SERVICE_UNAVAILABLE"); + fieldsMapping.put("TEMPORARY_REDIRECT", "TEMPORARY_REDIRECT"); + fieldsMapping.put("TOO_MANY_REQUESTS", "TOO_MANY_REQUESTS"); + fieldsMapping.put("UNAUTHORIZED", "UNAUTHORIZED"); + fieldsMapping.put("UNSUPPORTED_MEDIA_TYPE", "UNSUPPORTED_MEDIA_TYPE"); + fieldsMapping.put("USE_PROXY", "USE_PROXY"); + + fieldsMapping.forEach( + (key, value) -> doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.Response$Status." + key,"org.springframework.http.HttpStatus." + value)) + ); + + // Instance methods + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType getStatusCode()") + .or(methodInvocationMatcher("javax.ws.rs.core.Response.Status getStatusCode()")), + (v, m, addImport) -> { + return m.withName(m.getName().withSimpleName("getValue")); + })); + + // Remove #toEnum() method calls - these shouldn't appear as we migrate both Jax-Rs Status and StatusType to the same HttpStatus + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType toEnum()").or(methodInvocationMatcher("javax.ws.rs.core.Response.Status toEnum()")), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.HttpStatus)}").build(); + return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + })); + + // Switch Family to Series + doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType getFamily()").or(methodInvocationMatcher("javax.ws.rs.core.Response.Status getFamily()")), (v, m, addImport) -> { + return m.withName(m.getName().withSimpleName("series")); + })); + + // getReasonPhrase() doesn't need to be migrated - same named method returning the same type + + // Type reference replacement + + doNext(new ChangeType("javax.ws.rs.core.Response$StatusType", "org.springframework.http.HttpStatus", false)); + doNext(new ChangeType("javax.ws.rs.core.Response$Status", "org.springframework.http.HttpStatus", false)); + } + + @Override + public String getDisplayName() { + return "Swap Jax-RS Status with Spring HttpStatus"; + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java new file mode 100644 index 000000000..c171cbacd --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021 - 2023 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/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/RemoveAnnotationIfAccompaniedVisitor.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/RemoveAnnotationIfAccompaniedVisitor.java new file mode 100644 index 000000000..845173830 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/RemoveAnnotationIfAccompaniedVisitor.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021 - 2023 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/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/GenerateWebServices.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/GenerateWebServices.java new file mode 100644 index 000000000..7c891d848 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/GenerateWebServices.java @@ -0,0 +1,246 @@ +/* + * Copyright 2021 - 2023 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.jaxws; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.sbm.build.api.Module; +import org.springframework.sbm.build.impl.OpenRewriteMavenPlugin; +import org.springframework.sbm.build.impl.OpenRewriteMavenPlugin.OpenRewriteMavenPluginExecution; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.recipe.UserInteractions; +import org.springframework.sbm.java.api.*; +import org.springframework.sbm.java.migration.conditions.HasAnnotation; +import org.springframework.sbm.engine.context.ProjectContext; +import freemarker.template.Configuration; +import freemarker.template.Template; +import org.apache.commons.io.FilenameUtils; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.xml.XmlParser; +import org.openrewrite.xml.tree.Xml; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + + +/** + * @author Alex Boyko + */ +public class GenerateWebServices extends AbstractAction { + + private static final String PROPERTY_KEY_JAVA_GEN_FOLDER = "generated-sources.dir"; + private static final String PROPERTY_VALUE_JAVA_GEN_FOLDER = "src/generated"; + public static final String QUESTION = "Provide the path to WSDL file for Web Service '%s'"; + + @JsonIgnore + private final UserInteractions ui; + + @JsonIgnore + private final Configuration configuration; + + @Autowired + @JsonIgnore + private ExecutionContext executionContext; + + public GenerateWebServices(UserInteractions ui, Configuration configuration) { + this.ui = ui; + this.configuration = configuration; + setCondition(HasAnnotation.builder().annotation(WebServiceDescriptor.WEB_SERVICE_ANNOTATION).build()); + setDescription("Generate XML message schema file from Jax-WS Web-Service annotated types"); + } + + @Override + public void apply(ProjectContext context) { + // TODO: find applicable module here + context.getApplicationModules().stream() + .forEach(module -> { + List wsDescriptors = module.getMainJavaSourceSet().list().stream() + .flatMap(js -> js.getTypes().stream()) + .filter(c -> c.hasAnnotation(WebServiceDescriptor.WEB_SERVICE_ANNOTATION)) + .filter(c -> c.getKind() == KindOfType.CLASS) + .map(typeAnnotatedAsWebService -> this.processType(module, typeAnnotatedAsWebService)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (!wsDescriptors.isEmpty()) { + + generateWebConfig(module, wsDescriptors); + + List noPojoPresentWsDescriptors = wsDescriptors.stream() + .filter(d -> !d.isPojoCodePresent(module)) + .collect(Collectors.toList()); + + if (!noPojoPresentWsDescriptors.isEmpty()) { + addMavenPluginForJavaSourceGeneration(module, noPojoPresentWsDescriptors); + } + + wsDescriptors.forEach(wsd -> { + wsd.generateEndpoint(module); + wsd.removeJeeAnnotations(); + }); + } + }); + } + + private void addMavenPluginForJavaSourceGeneration(Module module, List descriptors) { + module.getBuildFile().setProperty(PROPERTY_KEY_JAVA_GEN_FOLDER, PROPERTY_VALUE_JAVA_GEN_FOLDER); + + List generateExecs = descriptors.stream().map(d -> OpenRewriteMavenPluginExecution.builder() + .goal("generate") + .configuration("\n\n" + + "${project.basedir}/src/main/resources\n" + + "*.wsdl\n" + + "${generated-sources.dir}\n" + + "" + d.getPackageName() + "" + + "\n") + .build() + ).collect(Collectors.toList()); + + module.getBuildFile().addPlugin( + OpenRewriteMavenPlugin.builder() + .groupId("org.jvnet.jaxb2.maven2") + .artifactId("maven-jaxb2-plugin") + .version("0.14.0") + .executions(generateExecs) + .build() + ); + + module.getBuildFile().addPlugin( + OpenRewriteMavenPlugin.builder() + .groupId("org.codehaus.mojo") + .artifactId("build-helper-maven-plugin") + .execution(OpenRewriteMavenPluginExecution.builder() + .goal("add-source") + .phase("generate-sources") + .configuration("\n\n" + + "\n" + + "${generated-sources.dir}\n" + + "\n" + + "\n") + .build() + ) + .build() + ); + } + + private void generateWebConfig(Module module, List wsDescriptors) { + JavaSourceLocation location = module.getMainJavaSourceSet().getJavaSourceLocation(); + + Map params = new HashMap<>(); + params.put("packageName", location.getPackageName()); + params.put("className", "WebServiceConfig"); + params.put("wsdls", wsDescriptors.stream() + .map(d -> { + Map props = new HashMap<>(); + String fileName = d.getWsdl().getSourcePath().getFileName().toString(); + props.put("file", fileName); + props.put("methodName", FilenameUtils.removeExtension(fileName)); + props.put("beanName", d.getWsdlDefBeanName()); + props.put("contextPath", d.getPathContext()); + return props; + }) + .collect(Collectors.toList()) + ); + + StringWriter writer = new StringWriter(); + try { + Template template = configuration.getTemplate("jaxws-web-config.ftl"); + template.process(params, writer); + } catch (Exception e) { + throw new RuntimeException(e); + } + + String src = writer.toString(); + module.getMainJavaSourceSet().addJavaSource(module.getProjectRootDirectory(), location.getSourceFolder(), src, location.getPackageName()); + } + + private WebServiceDescriptor processType(Module module, Type typeAnnotatedAsWebService) { + Type effectiveType = getEndpointInterfaceTypeType(module, typeAnnotatedAsWebService); + Xml.Document wsdl = createWsdlFile(module, typeAnnotatedAsWebService); + if (wsdl != null) { + return new WebServiceDescriptor(typeAnnotatedAsWebService, effectiveType, wsdl, configuration); + } else { + System.out.println("Skipping Web Service '" + effectiveType.getSimpleName() + "'..."); + return null; + } + } + + private Xml.Document createWsdlFile(Module module, Type type) { + Path p = null; + String input = null; + while (p == null) { + input = ui.askForInput(String.format(QUESTION, type.getSimpleName())); + if (input.isEmpty()) { + return null; + } + p = Path.of(input); + try { + Xml.Document doc = parseWsdl(p, executionContext); + if (doc.getRoot() == null) { + throw new Exception("Invalid WSDL file. It has either invalid or no XML content."); + } + String filename = type.getSimpleName().toLowerCase() + ".wsdl"; + module.getMainResourceSet().addStringResource(filename, doc.printAll()); + return doc; + } catch (Exception e) { + // TODO: Replace System.out.println, see #175 + System.out.println("Error processing WSDL file '" + p + "'. " + e.getMessage() + "\nTry entering file path again or press 'Enter' to cancel migrating this Web Service"); + p = null; + } + } + throw new RuntimeException(String.format("Could not create WSDL file from given input '%s'", input)); + } + + public static Xml.Document parseWsdl(Path p, ExecutionContext executionContext) { + Parser.Input o = new Parser.Input(p, () -> { + try { + return Files.newInputStream(p); + } catch (IOException e) { + return new ByteArrayInputStream(e.getMessage().getBytes()); + } + }); + List docs = XmlParser.builder().build().parseInputs(Collections.singleton(o), (Path)null, executionContext).toList(); + if (docs.isEmpty()) { + throw new RuntimeException("Failed to parse XML file '" + p + "'"); + } else { + return (Xml.Document) docs.get(0); + } + } + + private Type getEndpointInterfaceTypeType(Module module, Type type) { + Annotation annotation = type.getAnnotations().stream().filter(a -> WebServiceDescriptor.WEB_SERVICE_ANNOTATION.equals(a.getFullyQualifiedName())).findFirst().get(); + Expression endPointInterfaceExpression = annotation.getAttribute("endpointInterface"); + if (endPointInterfaceExpression != null) { + String endpointInterfaceFqName = endPointInterfaceExpression.getAssignmentRightSide().print().replace("\"", ""); + return new SuperTypeHierarchy(type).getRoot().getSuperTypes().stream() + .map(shn -> shn.getNode()) + .filter(t -> t.getKind() == KindOfType.INTERFACE) + .filter(t -> endpointInterfaceFqName.equals(t.getFullyQualifiedName())) + .findFirst() + .orElse(null); + } + return null; + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/MigrateJaxWsRecipe.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/MigrateJaxWsRecipe.java new file mode 100644 index 000000000..b6b720471 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/MigrateJaxWsRecipe.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021 - 2023 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.jaxws; + +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.build.migration.actions.AddDependencies; +import org.springframework.sbm.build.migration.conditions.NoExactDependencyExist; +import org.springframework.sbm.engine.recipe.UserInteractions; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.java.migration.conditions.HasImportStartingWith; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class MigrateJaxWsRecipe { + @Bean + public Recipe jaxWs(UserInteractions ui, freemarker.template.Configuration configuration) { + GenerateWebServices generateWebServices = new GenerateWebServices(ui, configuration); + generateWebServices.setDescription("Generate WebServices"); + return Recipe.builder() + .name("migrate-jax-ws") + .order(60) + .description("Migrate Jax Web-Service implementation to Spring Boot bases Web-Service") + .condition(HasImportStartingWith.builder().value("javax.jws.WebService").description("Has jax-ws WebService import").build()) + .actions(List.of( + AddDependencies.builder() + .dependencies( + List.of( + Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-web-services").version("latest.release").build() + ) + ) + .description("Add spring boot web-services starter") + .condition(NoExactDependencyExist.builder().dependency(Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-web-services").build()).build()) + .build(), + + generateWebServices + )) + .build(); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java new file mode 100644 index 000000000..f238063c4 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java @@ -0,0 +1,295 @@ +/* + * Copyright 2021 - 2023 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.jaxws; + +import org.springframework.sbm.build.api.Module; +import org.springframework.sbm.java.api.*; +import freemarker.template.Configuration; +import freemarker.template.Template; +import lombok.Getter; +import org.openrewrite.Recipe; +import org.openrewrite.java.OrderImports; +import org.openrewrite.java.RemoveAnnotation; +import org.openrewrite.java.format.AutoFormat; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; +import org.springframework.util.StringUtils; + +import java.io.StringWriter; +import java.net.URI; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Alex Boyko + */ +@Getter +class WebServiceDescriptor { + + public static final String WEB_SERVICE_ANNOTATION = "javax.jws.WebService"; + private static final String WEB_METHOD_ANNOTATION = "javax.jws.WebMethod"; + private static final String WEB_PARAM_ANNOTATION = "javax.jws.WebParam"; + private static final String WEB_RESULT_ANNOTATION = "javax.jws.WebResult"; + private static final String ONE_WAY_ANNOTATION = "javax.jws.Oneway"; + + private static final String NSURI_CONSTANT_NAME = "NAMESPACE_URI"; + public static final String HTTP_SCHEMAS_XML_WSDL = "http://schemas.xmlsoap.org/wsdl/"; + public static final String HTTP_SCHEMAS_XML_SOAP = "http://schemas.xmlsoap.org/wsdl/soap/"; + public static final String WS = "ws"; + + private final Type typeAnnotatedAsWebService; + + private final Type endpointInterface; + + private final Xml.Document wsdl; + + private final Configuration configuration; + + private String nsUri; + + private String packageName; + + private Type serviceType; + + private String serviceFieldName; + + private String pathContext; + + private String wsdlDefBeanName; + + public WebServiceDescriptor(Type typeAnnotatedAsWebService, Type effectiveInterface, Xml.Document wsdl, Configuration configuration) { + this.typeAnnotatedAsWebService = typeAnnotatedAsWebService; + this.endpointInterface = effectiveInterface; + this.wsdl = wsdl; + this.configuration = configuration; + init(); + } + + private void init() { + nsUri = computeNsUri(); + packageName = computePackageName(); + serviceType = endpointInterface == null ? typeAnnotatedAsWebService : endpointInterface; + if (serviceType != null) { + serviceFieldName = StringUtils.uncapitalize(serviceType.getSimpleName()); + } + initPathContext(); + } + + private String computeNsUri() { + if (wsdl != null) { + return wsdl.getRoot().getAttributes().stream().filter(a -> "targetNamespace".equals(a.getKeyAsString())).map(a -> a.getValueAsString()).findFirst().orElse(null); + } else { + return Stream.concat(endpointInterface == null ? Stream.empty() : endpointInterface.getAnnotations().stream(), typeAnnotatedAsWebService.getAnnotations().stream()) + .filter(a -> WEB_SERVICE_ANNOTATION.equals(a.getFullyQualifiedName())) + .map(a -> a.getAttribute("targetNamespace")) + .filter(Objects::nonNull) + .map(e -> e.getAssignmentRightSide()) + .map(e -> e.print()) + .map(s -> s.replace("\"", "")) + .findFirst() + .orElse(null); + } + } + + private String computePackageName() { + if (nsUri != null) { + URI uri = URI.create(nsUri); + String host = uri.getHost(); + List pkgNameTokens = new ArrayList<>(); + String[] tokens = host.split("\\."); + for (int i = tokens.length - 1; i >= 0; i--) { + pkgNameTokens.add(tokens[i]); + } + for (String t : uri.getPath().split("/")) { + if (!t.isEmpty()) { + pkgNameTokens.add(t); + } + } + return String.join(".", pkgNameTokens); + } + return "com.sbm.generated"; + } + + + public JavaSource generateEndpoint(Module module) { + List ops = (endpointInterface == null ? typeAnnotatedAsWebService.getMethods().stream().filter(m -> m.getVisibility() == Visibility.PUBLIC) : endpointInterface.getMethods().stream()) + .map(this::createWebServiceOperation) + .collect(Collectors.toList()); + + JavaSource javaSource = generateEndpointSource(module, ops); + + javaSource.apply(new OrderImports(false).doNext(new AutoFormat())); + + return javaSource; + } + + private WebServiceOperation createWebServiceOperation(Method m) { + WebServiceOperation op = new WebServiceOperation(); + + String name = m.getAnnotation(WEB_METHOD_ANNOTATION) + .map(a -> a.getAttribute("operationName")) + .filter(Objects::nonNull) + .map(e -> e.getAssignmentRightSide()) + .map(e -> e.print()) + .map(s -> s.replace("\"", "")) + .orElse(m.getName()); + op.setName(name); + op.setServiceMethodName(m.getName()); + + + WebServiceOperation.Type output = null; + if (!m.getAnnotation(ONE_WAY_ANNOTATION).isPresent()) { + output = new WebServiceOperation.Type(); + output.setPackageName(packageName); + output.setSimpleName(StringUtils.capitalize(op.getName()) + "Response"); + output.setFields(new String[]{ + m.getAnnotation(WEB_RESULT_ANNOTATION) + .map(a -> a.getAttribute("name")) + .filter(Objects::nonNull) + .map(e -> e.getAssignmentRightSide()) + .map(e -> e.print()) + .map(s -> s.replace("\"", "")) + .orElse("return") + }); + } + op.setOutput(output); + + WebServiceOperation.Type input = new WebServiceOperation.Type(); + input.setPackageName(packageName); + input.setSimpleName(StringUtils.capitalize(op.getName())); + String[] fields = new String[m.getParams().size()]; + for (int i = 0; i < m.getParams().size(); i++) { + MethodParam param = m.getParams().get(i); + fields[i] = param.getAnnotations().stream() + .filter(a -> WEB_PARAM_ANNOTATION.equals(a.getFullyQualifiedName())) + .map(a -> a.getAttribute("name")) + .filter(Objects::nonNull) + .map(e -> e.getAssignmentRightSide()) + .map(e -> e.print()) + .map(s -> s.replace("\"", "")) + .findFirst() + .orElse("arg" + i); + } + input.setFields(fields); + + op.setInput(input); + + return op; + } + + private JavaSource generateEndpointSource(Module module, List ops) { + JavaSource clazzSource = module.getMainJavaSourceSet() + .stream() + .filter(js -> js.getTypes().stream().filter(t -> typeAnnotatedAsWebService.getFullyQualifiedName().equals(t.getFullyQualifiedName())).findFirst().isPresent()) + .findFirst() + .get(); + + String packageName = clazzSource.getPackageName(); + Path sourceFolder = clazzSource.getSourceFolder(); + if (sourceFolder == null) { + JavaSourceLocation location = module.getMainJavaSourceSet().getJavaSourceLocation(); + sourceFolder = location.getSourceFolder(); + packageName = location.getPackageName(); + } + + Map params = new HashMap<>(); + params.put("packageName", packageName); + params.put("className", serviceType.getSimpleName() + "Endpoint"); + params.put("serviceFqName", serviceType.getFullyQualifiedName()); + params.put("serviceSimpleName", serviceType.getSimpleName()); + params.put("serviceFieldName", serviceFieldName); + params.put("nsUriConstantName", NSURI_CONSTANT_NAME); + params.put("nsUri", nsUri); + + params.put("operations", ops.stream().map(op -> op.createSnippet(serviceFieldName, NSURI_CONSTANT_NAME)).collect(Collectors.toList())); + params.put("imports", ops.stream().flatMap(op -> Arrays.stream(op.getImports())).collect(Collectors.toSet())); + + StringWriter writer = new StringWriter(); + try { + Template template = configuration.getTemplate("jaxws-endpoint.ftl"); + template.process(params, writer); + } catch (Exception e) { + throw new RuntimeException(e); + } + + String src = writer.toString(); + return module.getMainJavaSourceSet().addJavaSource(module.getProjectRootDirectory(), sourceFolder, src, packageName); + } + + public void removeJeeAnnotations() { + Recipe recipe = new RemoveAnnotation(WEB_SERVICE_ANNOTATION) + .doNext(new RemoveAnnotation(WEB_METHOD_ANNOTATION)) + .doNext(new RemoveAnnotation(WEB_PARAM_ANNOTATION)) + .doNext(new RemoveAnnotation(WEB_RESULT_ANNOTATION)) + .doNext(new RemoveAnnotation(ONE_WAY_ANNOTATION)); + + typeAnnotatedAsWebService.apply(recipe); + if (endpointInterface != null) { + endpointInterface.apply(recipe); + } + } + + public void initPathContext() { + if (wsdl != null) { + String wsdlNsPrefix = XmlUtils.findNsPrefix(wsdl.getRoot(), HTTP_SCHEMAS_XML_WSDL); + String soapNsPrefix = XmlUtils.findNsPrefix(wsdl.getRoot(), HTTP_SCHEMAS_XML_SOAP); + if (wsdlNsPrefix != null && soapNsPrefix != null) { + String xpath = String.format("/%1$s:definitions/%1$s:service/%1$s:port/%2$s:address", wsdlNsPrefix, soapNsPrefix); + XmlUtils.getFirstTagAttribute(wsdl, new XPathMatcher(xpath), "location") + .map(URI::create) + .map(uri -> Path.of(uri.getPath())) + .ifPresent(p -> { + pathContext = p.subpath(0, p.getNameCount() - 1).toString(); + wsdlDefBeanName = p.subpath(p.getNameCount() - 1, p.getNameCount()).toString(); + }); + } + if (wsdlNsPrefix == null && soapNsPrefix != null) { + String xpath = String.format("/definitions/service/port/%1$s:address", soapNsPrefix); + XmlUtils.getFirstTagAttribute(wsdl, new XPathMatcher(xpath), "location") + .map(URI::create) + .map(uri -> Path.of(uri.getPath())) + .ifPresent(p -> { + pathContext = p.subpath(0, p.getNameCount() - 1).toString(); + wsdlDefBeanName = p.subpath(p.getNameCount() - 1, p.getNameCount()).toString(); + }); + } + + } + if (pathContext == null) { + pathContext = "ws"; + if (endpointInterface != null) { + wsdlDefBeanName = StringUtils.uncapitalize(endpointInterface.getSimpleName()); + return; + } + String clazzName = StringUtils.uncapitalize(typeAnnotatedAsWebService.getSimpleName()); + String postfix = clazzName.substring(clazzName.length() - WS.length()); + if (WS.equalsIgnoreCase(postfix)) { + wsdlDefBeanName = clazzName.substring(0, clazzName.length() - WS.length()); + } else { + wsdlDefBeanName = clazzName + WS; + } + } + } + + public boolean isPojoCodePresent(Module module) { + return module.getMainJavaSourceSet().list().stream() + .filter(js -> packageName.equals(js.getPackageName())) + .findFirst() + .isPresent(); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceOperation.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceOperation.java new file mode 100644 index 000000000..762228fde --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceOperation.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021 - 2023 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.jaxws; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * @author Alex Boyko + */ +@Getter +@Setter +public class WebServiceOperation { + + private String name; + + private String serviceMethodName; + + private Type input; + + private Type output; + + @Getter + @Setter + public static class Type { + private String packageName; + private String simpleName; + private String[] fields; + + public String getFqName() { + return packageName + "." + simpleName; + } + } + + public String createSnippet(String serviceFieldName, String nsUriConstant) { + StringBuilder sb = new StringBuilder(); + sb.append("@PayloadRoot(namespace = " + nsUriConstant + ", localPart = \"" + StringUtils.uncapitalize(input.getSimpleName()) + "\")\n"); + String params = Arrays.stream(input.fields).map(f -> "request.get" + StringUtils.capitalize(f) + "()").collect(Collectors.joining(", ")); + String serviceCall = serviceFieldName + "." + serviceMethodName + "(" + params + ")"; + if (output == null) { + sb.append("public void " + name + "(@RequestPayload " + input.getSimpleName() + " request) {\n"); + sb.append("\t" + serviceCall + ";\n"); + sb.append("}\n"); + } else { + sb.append("@ResponsePayload\n"); + sb.append("public " + output.simpleName + " " + name + "(@RequestPayload " + input.getSimpleName() + " request) {\n"); + sb.append("\t" + output.simpleName + " response = new " + output.getSimpleName() + "();\n"); + sb.append("\tresponse.set" + StringUtils.capitalize(output.fields[0]) + "(" + serviceCall + ");\n"); + sb.append("\treturn response;\n"); + sb.append("}\n"); + } + return sb.toString(); + } + + public String[] getImports() { + if (output == null) { + return new String[]{ + "org.springframework.ws.server.endpoint.annotation.PayloadRoot", + "org.springframework.ws.server.endpoint.annotation.RequestPayload", + input.getFqName(), + }; + } else { + return new String[]{ + "org.springframework.ws.server.endpoint.annotation.PayloadRoot", + "org.springframework.ws.server.endpoint.annotation.RequestPayload", + "org.springframework.ws.server.endpoint.annotation.ResponsePayload", + input.getFqName(), + output.getFqName() + }; + } + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/XmlUtils.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/XmlUtils.java new file mode 100644 index 000000000..fdfcb8e50 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/XmlUtils.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 - 2023 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.jaxws; + +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.XmlVisitor; +import org.openrewrite.xml.tree.Xml; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @author Alex Boyko + */ +public class XmlUtils { + + public static final String XMLNS_ATTR_PREFIX = "xmlns:"; + + public static List findTags(Xml.Document doc, XPathMatcher matcher) { + final List context = new ArrayList<>(); + new XmlVisitor>() { + @Override + public Xml visitTag(Xml.Tag tag, List found) { + if (matcher.matches(getCursor())) { + context.add(tag); + } + return super.visitTag(tag, found); + } + }.visit(doc, context); + return context; + } + + public static Optional getFirstTagAttribute(Xml.Document doc, XPathMatcher matcher, String attr) { + List tags = findTags(doc, matcher); + return tags.isEmpty() ? Optional.empty() : tags.get(0).getAttributes().stream().filter(a -> attr.equals(a.getKeyAsString())).map(a -> a.getValueAsString()).findFirst(); + } + + public static String findNsPrefix(Xml.Tag tag, String nsUri) { + return tag.getAttributes().stream().map(a -> { + if (a.getKeyAsString().startsWith(XMLNS_ATTR_PREFIX) && nsUri.equals(a.getValueAsString())) { + return a.getKeyAsString().substring(XMLNS_ATTR_PREFIX.length()); + } + return ""; + }).filter(s -> !s.isEmpty()).findFirst().orElse(null); + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jms/actions/AddJmsConfigAction.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jms/actions/AddJmsConfigAction.java new file mode 100644 index 000000000..e6c0d8283 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jms/actions/AddJmsConfigAction.java @@ -0,0 +1,107 @@ +/* + * Copyright 2021 - 2023 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.jms.actions; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import freemarker.template.Configuration; +import freemarker.template.Template; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.sbm.build.MultiModuleApplicationNotSupportedException; +import org.springframework.sbm.build.api.Module; +import org.springframework.sbm.build.api.JavaSourceSet; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.Annotation; +import org.springframework.sbm.java.api.Expression; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.JavaSourceLocation; +import org.springframework.sbm.java.api.Member; +import org.springframework.sbm.java.api.Type; + +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class AddJmsConfigAction extends AbstractAction { + + @Autowired + @Setter + @JsonIgnore + private Configuration configuration; + + @Override + public void apply(ProjectContext context) { + + if (context.getApplicationModules().isSingleModuleApplication()) { + Module module = context.getApplicationModules().getRootModule(); + applyToModule(module); + } else { + throw new MultiModuleApplicationNotSupportedException("Action can only be applied to applications with single module, but multiple build files were found: ['" + context.getApplicationModules().stream().map(am -> am.getBuildFile().getAbsolutePath().toString()).collect(Collectors.joining("', '")) + "']"); + } + + } + + private void applyToModule(Module module) { + JavaSourceSet mainJavaSourceSet = module.getMainJavaSourceSet(); + JavaSourceLocation location = mainJavaSourceSet.getJavaSourceLocation(); + + String className = "JmsConfig"; + + Map params = new HashMap<>(); + params.put("packageName", location.getPackageName()); + params.put("className", className); + Map allQueues = findAllQueues(mainJavaSourceSet); + params.put("queues", allQueues); + + try(StringWriter writer = new StringWriter()) { + Template template = configuration.getTemplate("jms-config.ftl"); + template.process(params, writer); + String src = writer.toString(); + mainJavaSourceSet.addJavaSource(module.getProjectRootDirectory(), src, location.getPackageName()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private Map findAllQueues(JavaSourceSet javaSourceSet) { + Map queues = new HashMap<>(); + for (JavaSource js : javaSourceSet.list()) { + for (Type t : js.getTypes()) { + for (Member m : t.getMembers()) { + if ("javax.jms.Queue".equals(m.getTypeFqName())) { + Annotation annotation = m.getAnnotation("javax.annotation.Resource"); + if (annotation != null) { + Expression queueNameExp = annotation.getAttributes().get("name"); + if (queueNameExp != null) { + Expression valueExpr = queueNameExp.getAssignmentRightSide(); + if (valueExpr != null) { + queues.put(m.getName(), valueExpr.print()); + } + } + } + } + + } + } + } + return queues; + } + + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jms/actions/ReplaceMdbAnnotationWithJmsListener.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jms/actions/ReplaceMdbAnnotationWithJmsListener.java new file mode 100644 index 000000000..06dc744f9 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jms/actions/ReplaceMdbAnnotationWithJmsListener.java @@ -0,0 +1,97 @@ +/* + * Copyright 2021 - 2023 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.jms.actions; + +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.Annotation; +import org.springframework.sbm.java.api.Expression; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.Type; +import org.springframework.sbm.engine.context.ProjectContext; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ReplaceMdbAnnotationWithJmsListener extends AbstractAction { + + @Override + public void apply(ProjectContext context) { + for (JavaSource js : context.getProjectJavaSources().list()) { + for (Type t : js.getTypes()) { + if (t.hasAnnotation("javax.ejb.MessageDriven") + && t.isTypeOf("javax.jms.MessageListener")) { + transform(js, t); + } + } + js.replaceLiteral(String.class, (old) -> { + if (old instanceof String) { + return old.replace("java:app/jms/", ""); + } + return old; + }); + } + } + + private void transform(JavaSource js, Type t) { + Map mdbProperties = new HashMap<>(); + t.getAnnotations().stream().filter(a -> "javax.ejb.MessageDriven".equals(a.getFullyQualifiedName())).findFirst().ifPresent(a -> { + Expression e = a.getAttribute("activationConfig"); + if (e != null) { + List annotations = js.getAnnotations("javax.ejb.ActivationConfigProperty", e); + for (Annotation annotation : annotations) { + Expression key = annotation.getAttribute("propertyName"); + Expression value = annotation.getAttribute("propertyValue"); + mdbProperties.put(key.getAssignmentRightSide().print(), value.getAssignmentRightSide()); + } + } + }); + + t.removeAnnotation("javax.ejb.MessageDriven"); + + t.removeImplements("javax.jms.MessageListener"); + + t.addAnnotation("@Component", "org.springframework.stereotype.Component"); + + t.getMethods().stream() + .filter(method -> "onMessage".equals(method.getName()) && method.getParams().size() == 1) + .findFirst() + .ifPresent(method -> { + boolean shouldAddJmsListener = true; + for (Annotation oldAnnotation : method.getAnnotations()) { + switch (oldAnnotation.getFullyQualifiedName()) { + case "java.lang.Override": + method.removeAnnotation(oldAnnotation); + break; + case "org.springframework.jms.annotation.JmsListener": + shouldAddJmsListener = false; + break; + default: + } + } + if (shouldAddJmsListener) { + Expression e = mdbProperties.containsKey("\"destination\"") ? mdbProperties.get("\"destination\"") : mdbProperties.get("\"destinationLookup\""); + if (e != null) { + method.addAnnotation("@JmsListener(destination = " + e.print() + ")", "org.springframework.jms.annotation.JmsListener"); + } else { + method.addAnnotation("@JmsListener", "org.springframework.jms.annotation.JmsListener"); + } + } + }); + + } + +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jsf/actions/AddJoinfacesDependencies.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jsf/actions/AddJoinfacesDependencies.java new file mode 100644 index 000000000..d8af3df77 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jsf/actions/AddJoinfacesDependencies.java @@ -0,0 +1,147 @@ +/* + * Copyright 2021 - 2023 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.jsf.actions; + +import org.springframework.sbm.build.MultiModuleApplicationNotSupportedException; +import org.springframework.sbm.build.api.Module; +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.context.ProjectContext; +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class AddJoinfacesDependencies extends AbstractAction { + + @Override + public void apply(ProjectContext context) { + if (context.getApplicationModules().isSingleModuleApplication()) { + Module module = context.getApplicationModules().getRootModule(); + applyToModule(module); + } else { + throw new MultiModuleApplicationNotSupportedException("Action can only be applied to applications with single module."); + } + } + + private void applyToModule(Module module) { + JsfImplementation jsfImplementation = getJsfImplementationInUse(module); + + if (jsfImplementation.equals(JsfImplementation.UNKNOWN)) { + log.warn("Could not find used JSF implementation. Currently supported implementations are [" + JsfImplementation.supportedImplementations() + "]"); + return; + } + + addJoinfacesDependencyManagement(module); + addJoinfacesDependencies(jsfImplementation, module); + } + + @Override + public boolean isApplicable(ProjectContext context) { + if (context.getApplicationModules().isSingleModuleApplication()) { + return hasJsfImport(context.getApplicationModules().getRootModule()); + } + return false; + } + + private void addJoinfacesDependencies(JsfImplementation jsfImplementation, Module module) { + if (jsfImplementation.equals(JsfImplementation.APACHE_MYFACES)) { + addMyFacesDependencies(module); + } else if (jsfImplementation.equals(JsfImplementation.MOJARRA)) { + addMojarraDependencies(module); + } + } + + private void addMojarraDependencies(Module context) { + Dependency joinfacesStarter = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("jsf-spring-boot-starter") + .version("4.4.10") + .build(); + + context.getBuildFile().addDependencies(List.of(joinfacesStarter)); + } + + private void addMyFacesDependencies(Module context) { + Dependency joinfacesStarter = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("jsf-spring-boot-starter") + .version("4.4.10") + .exclusions(List.of(Dependency.builder() + .groupId("org.joinfaces") + .artifactId("mojarra-spring-boot-starter") + .build()) + ) + .build(); + + Dependency myfacesStarter = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("myfaces-spring-boot-starter") + .version("4.4.10") + .build(); + + context.getBuildFile().addDependencies(List.of(joinfacesStarter, myfacesStarter)); + } + + private void addJoinfacesDependencyManagement(Module context) { + Dependency joinfacesDependencyManagement = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("joinfaces-dependencies") + .version("4.4.10") + .type("pom") + .scope("import") + .build(); + context.getBuildFile().addToDependencyManagement(joinfacesDependencyManagement); + } + + private JsfImplementation getJsfImplementationInUse(Module module) { + if (usesMyFaces(module)) { + return JsfImplementation.APACHE_MYFACES; + } else if (usesMojarra(module)) { + return JsfImplementation.MOJARRA; + } else { + return JsfImplementation.UNKNOWN; + } + } + + private boolean usesMojarra(Module module) { + return module.getBuildFile().hasDeclaredDependencyMatchingRegex( + "org\\.glassfish\\:javax\\.faces.*", + "org\\.glassfish\\:jakarta\\.faces.*"); + } + + private boolean usesMyFaces(Module module) { + return module.getBuildFile().hasDeclaredDependencyMatchingRegex("org\\.apache\\.myfaces.*"); + } + + private boolean hasJsfImport(Module module) { + return module.getMainJavaSourceSet().hasImportStartingWith("javax.faces"); + } + + private enum JsfImplementation { + APACHE_MYFACES, + MOJARRA, + UNKNOWN; + + public static String supportedImplementations() { + return Arrays.asList(JsfImplementation.values()).stream() + .map(jsfimpl -> jsfimpl.name()) + .collect(Collectors.joining(", ")); + } + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jsf/conditions/IsMigrateJsf2ToSpringBootApplicableCondition.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jsf/conditions/IsMigrateJsf2ToSpringBootApplicableCondition.java new file mode 100644 index 000000000..6d16ccd0d --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jsf/conditions/IsMigrateJsf2ToSpringBootApplicableCondition.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 - 2023 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.jsf.conditions; + +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.build.migration.conditions.NoExactDependencyExist; +import org.springframework.sbm.engine.recipe.Condition; +import org.springframework.sbm.java.migration.conditions.HasImportStartingWith; +import org.springframework.sbm.engine.context.ProjectContext; + +public class IsMigrateJsf2ToSpringBootApplicableCondition implements Condition { + @Override + public String getDescription() { + return "Check if recipe 'migrate-jsf-2.x-to-spring-boot' is applicable"; + } + + @Override + public boolean evaluate(ProjectContext context) { + Dependency dependency = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("jsf-spring-boot-starter") + .build(); + NoExactDependencyExist noExactDependencyExistCondition = new NoExactDependencyExist(dependency); + HasImportStartingWith hasImportStartingWithCondition = new HasImportStartingWith("javax.faces", "Search for imports starting with 'javax.faces'"); + + return noExactDependencyExistCondition.evaluate(context) && hasImportStartingWithCondition.evaluate(context); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootAction.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootAction.java new file mode 100644 index 000000000..636ea2daf --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootAction.java @@ -0,0 +1,129 @@ +/* + * Copyright 2021 - 2023 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.tx.actions; + +import org.springframework.rewrite.resource.RewriteSourceFileHolder; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.Annotation; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.Method; +import org.springframework.sbm.java.api.Type; +import org.springframework.sbm.engine.context.ProjectContext; +import org.jetbrains.annotations.NotNull; +import org.openrewrite.java.tree.J; + +import java.util.List; +import java.util.stream.Collectors; + +public class MigrateJeeTransactionsToSpringBootAction extends AbstractAction { + + @Override + public void apply(ProjectContext context) { + List> relevantSources = collectRelevantSources(context); + replaceAnnotationsOnTypeLevel(relevantSources); + replaceAnnotationsOnMethodLevel(relevantSources); + relevantSources.forEach(r -> ((JavaSource) r).removeUnusedImports()); // because maybeRemoveImport() fails to remove import of TransactionManagementType in MigrateJeeTransactionsToSpringBootAction since 7.16.3 + } + + private List> collectRelevantSources(ProjectContext context) { + return context.getProjectJavaSources().list() + .stream() + .filter(this::filterTypesWithTransactionAnnotations) + .filter(r -> RewriteSourceFileHolder.class.isAssignableFrom(r.getClass())) + .map(r -> (RewriteSourceFileHolder) r) + .collect(Collectors.toList()); + } + + private boolean filterTypesWithTransactionAnnotations(JavaSource javaSource) { + List referencedTypes = javaSource.getReferencedTypes(); + return referencedTypes.contains("javax.ejb.TransactionAttribute") || referencedTypes.contains("javax.ejb.TransactionManagementType"); + } + + private void replaceAnnotationsOnMethodLevel(List> relevantSources) { + relevantSources.stream() + .map(JavaSource.class::cast) + .flatMap(js -> js.getTypes().stream()) + .flatMap(t -> t.getMethods().stream()) + .forEach(m -> this.transformMethodAnnotations(m)); + } + + private void replaceAnnotationsOnTypeLevel(List> relevantSources) { + relevantSources.stream() + .map(JavaSource.class::cast) + .flatMap(js -> js.getTypes().stream()) + .forEach(t -> { + transformTypeAnnotations(t); + }); + } + + private void transformTypeAnnotations(Type type) { + List annotations = type.getAnnotations(); + for (Annotation annotation : annotations) { + switch (annotation.getFullyQualifiedName()) { + case "javax.ejb.TransactionManagement": + type.removeAnnotation(annotation); + break; + case "javax.ejb.TransactionAttribute": + String assignment = annotation.getAttribute("value").printAssignment(); + String newAssignment = mapAssignment(assignment); + type.addAnnotation("@Transactional(propagation = Propagation." + newAssignment + ")", + "org.springframework.transaction.annotation.Transactional", + "org.springframework.transaction.annotation.Propagation"); + type.removeAnnotation(annotation); + break; + default: + } + } + } + + void transformMethodAnnotations(Method method) { + List annotations = method.getAnnotations(); + for (Annotation annotation : annotations) { + switch (annotation.getFullyQualifiedName()) { + case "javax.ejb.TransactionAttribute": + String assignment = annotation.getAttribute("value").printAssignment(); + String newAssignment = mapAssignment(assignment); + method.removeAnnotation(annotation); + method.addAnnotation("@Transactional(propagation = Propagation." + newAssignment + ")", + "org.springframework.transaction.annotation.Transactional", + "org.springframework.transaction.annotation.Propagation"); + break; + default: + } + } + } + + @NotNull + private String mapAssignment(String assignment) { + String newAssignment = "MANDATORY"; + if (assignment.contains("REQUIRES_NEW")) { + newAssignment = "REQUIRES_NEW"; + } + if (assignment.contains("REQUIRED")) { + newAssignment = "REQUIRED"; + } + if (assignment.contains("NOT_SUPPORTED")) { + newAssignment = "NOT_SUPPORTED"; + } + if (assignment.contains("NEVER")) { + newAssignment = "NEVER"; + } + if (assignment.contains("SUPPORTS")) { + newAssignment = "SUPPORTS"; + } + return newAssignment; + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/utils/AnnotationUtils.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/utils/AnnotationUtils.java new file mode 100644 index 000000000..a640ac2a4 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/utils/AnnotationUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 - 2023 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/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/web/MigrateServletDefinitionFromWebXmlToAnnotation.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/web/MigrateServletDefinitionFromWebXmlToAnnotation.java new file mode 100644 index 000000000..84eabe318 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/web/MigrateServletDefinitionFromWebXmlToAnnotation.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021 - 2023 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.web; + +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.context.ProjectContext; + +public class MigrateServletDefinitionFromWebXmlToAnnotation extends AbstractAction { + + @Override + public void apply(ProjectContext context) { + + } + + @Override + public boolean isApplicable(ProjectContext context) { + return super.isApplicable(context); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/web/conditions/ShouldAddServletComponentScan.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/web/conditions/ShouldAddServletComponentScan.java new file mode 100644 index 000000000..e4c0cdb64 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/web/conditions/ShouldAddServletComponentScan.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 - 2023 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.web.conditions; + +import org.springframework.sbm.engine.recipe.Condition; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation; + +import java.util.ArrayList; +import java.util.List; + +public class ShouldAddServletComponentScan implements Condition { + + @Override + public String getDescription() { + return "It returns true if the application:\n " + + "\t1. Is Spring boot application\n" + + "\t2. Uses any of these annotations [@WebListener, @WebServlet, @WebFilter]\n" + + "\t3. Doesn't contain @ServletComponentScan"; + } + + @Override + public boolean evaluate(ProjectContext context) { + Condition isSpringBootApplicationCondition = createIsSpringBootApplicationCondition(); + Condition annotatedWithServletScanComponentCondition = createAnnotatedWithServletScanComponentCondition(); + List hasWebComponentAnnotationConditions = createHasWebComponentAnnotation(); + + boolean isSpringBootApplication = isSpringBootApplicationCondition.evaluate(context); + boolean isNotAnnotatedWithServletComponent = !annotatedWithServletScanComponentCondition.evaluate(context); + boolean anyWebComponentAnnotationExists = hasWebComponentAnnotationConditions.stream().anyMatch(c -> c.evaluate(context)); + + return isSpringBootApplication && isNotAnnotatedWithServletComponent && anyWebComponentAnnotationExists; + } + + HasTypeAnnotation createIsSpringBootApplicationCondition() { + HasTypeAnnotation hasTypeAnnotation = new HasTypeAnnotation(); + hasTypeAnnotation.setAnnotation("org.springframework.boot.autoconfigure.SpringBootApplication"); + return hasTypeAnnotation; + } + + HasTypeAnnotation createAnnotatedWithServletScanComponentCondition() { + HasTypeAnnotation hasTypeAnnotation = new HasTypeAnnotation(); + hasTypeAnnotation.setAnnotation("org.springframework.boot.web.servlet.ServletComponentScan"); + return hasTypeAnnotation; + } + + List createHasWebComponentAnnotation() { + List webComponentAnnotations = List.of( + "javax.servlet.annotation.WebListener", + "javax.servlet.annotation.WebServlet", + "javax.servlet.annotation.WebFilter"); + + List conditions = new ArrayList<>(); + for (String webComponentAnnotation : webComponentAnnotations) { + HasTypeAnnotation hasTypeAnnotation = new HasTypeAnnotation(); + hasTypeAnnotation.setAnnotation(webComponentAnnotation); + conditions.add(hasTypeAnnotation); + } + return conditions; + } + + +} diff --git a/components/jaxrs-recipes/src/main/resources/recipes/documentation-actions.yaml b/components/jaxrs-recipes/src/main/resources/recipes/documentation-actions.yaml new file mode 100644 index 000000000..44230d0e3 --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/documentation-actions.yaml @@ -0,0 +1,9 @@ +- name: documentation-actions + description: Create Documentation for Actions + condition: + type: org.springframework.sbm.common.migration.conditions.FalseCondition + actions: + - type: org.springframework.sbm.common.migration.actions.DocumentationExtraction + description: Extract documentation. + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition \ No newline at end of file diff --git a/components/jaxrs-recipes/src/main/resources/recipes/mark-and-clean-remote-ejbs.yaml b/components/jaxrs-recipes/src/main/resources/recipes/mark-and-clean-remote-ejbs.yaml new file mode 100644 index 000000000..f0fc704ec --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/mark-and-clean-remote-ejbs.yaml @@ -0,0 +1,22 @@ +- name: mark-and-clean-remote-ejbs + description: "Search @Stateless EJBs implementing a @Remote interface" + order: 85 + condition: + description: 'Any class has import starting with javax.ejb.Stateless' + type: org.springframework.sbm.java.migration.conditions.HasImportStartingWith + value: javax.ejb.Remote + actions: + - type: org.springframework.sbm.search.recipe.actions.OpenRewriteJavaSearchAction + condition: + description: 'Any class has import starting with javax.ejb.Stateless' + type: org.springframework.sbm.java.migration.conditions.HasImportStartingWith + value: javax.ejb.Remote + description: Mark occurences of @javax.ejb.Remote on types with comment + rewriteRecipeDefinition: + type: specs.openrewrite.org/v1beta/recipe + name: com.vmware.my.name + recipeList: + - org.openrewrite.java.search.FindAnnotations: + annotationPattern: "@javax.ejb.Remote" + commentText: |- + SBM-FIXME: Remove @javax.ejb.Remote annotation and provide as Remote Api, maybe RESTful. \ No newline at end of file diff --git a/components/jaxrs-recipes/src/main/resources/recipes/migrate-annotated-servlets.yaml b/components/jaxrs-recipes/src/main/resources/recipes/migrate-annotated-servlets.yaml new file mode 100644 index 000000000..28f126d34 --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/migrate-annotated-servlets.yaml @@ -0,0 +1,42 @@ +- name: migrate-annotated-servlets + description: Allow Spring Boot to deploy servlets annotated with @WebServlet + order: 70 + condition: + description: Any class has import starting with javax.servlet + type: org.springframework.sbm.java.migration.conditions.HasTypeAnnotation + annotation: javax.servlet.annotation.WebServlet + actions: + - type: org.springframework.sbm.build.migration.actions.AddDependencies + condition: + type: org.springframework.sbm.build.migration.conditions.NoExactDependencyExist + dependency: + groupId: org.springframework.boot + artifactId: spring-boot-starter-web + dependencies: + - groupId: org.springframework.boot + artifactId: spring-boot-starter-web + version: 2.3.4.RELEASE + description: Add spring-boot-starter-web dependency to build file. + detailedDescription: |- + This code snippet is going to be added to the section in pom.xml + + + org.springframework.boot + spring-boot-starter-web + + + + - type: org.springframework.sbm.java.migration.actions.AddTypeAnnotationToTypeAnnotatedWith + condition: + type: org.springframework.sbm.jee.web.conditions.ShouldAddServletComponentScan + description: Add ServletComponentScan to Spring Boot. + annotation: org.springframework.boot.web.servlet.ServletComponentScan + annotatedWith: org.springframework.boot.autoconfigure.SpringBootApplication + + + - type: org.springframework.sbm.build.migration.actions.RemoveDependenciesMatchingRegex + dependenciesRegex: + - javax\.servlet\:servlet-api.* + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + description: Delete javax.servlet:servlet-api dependency from build file. diff --git a/components/jaxrs-recipes/src/main/resources/recipes/migrate-jms.yaml b/components/jaxrs-recipes/src/main/resources/recipes/migrate-jms.yaml new file mode 100644 index 000000000..e145a3a7a --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/migrate-jms.yaml @@ -0,0 +1,52 @@ +- name: migrate-jms + description: Convert JEE JMS app into Spring Boot JMS app + condition: + description: Any class has import starting with javax.ws.rs + type: org.springframework.sbm.java.migration.conditions.HasImportStartingWith + value: javax.jms + actions: + - type: org.springframework.sbm.build.migration.actions.AddDependencies + condition: + type: org.springframework.sbm.build.migration.conditions.NoExactDependencyExist + dependency: + groupId: org.springframework.boot + artifactId: spring-boot-starter-activemq + dependencies: + - groupId: org.springframework.boot + artifactId: spring-boot-starter-activemq + version: 2.3.4.RELEASE + description: Add spring-boot-starter-activemq dependency to build file. + detailedDescription: |- + This code snippet is going to be added to the section in pom.xml + + + org.springframework.boot + spring-boot-starter-activemq + + + - type: org.springframework.sbm.jee.jms.actions.AddJmsConfigAction + condition: + type: org.springframework.sbm.java.migration.conditions.HasNoTypeAnnotation + annotation: 'org.springframework.jms.annotation.EnableJms' + description: Add a JmsListenerContainerFactory bean definition. + detailedDescription: |- + Add a JmsListenerContainerFactory bean definition + + public class JmsConfig { + + @Bean + public JmsListenerContainerFactory jmsListenerContainerFactory( + ConnectionFactory connectionFactory, + DefaultJmsListenerContainerFactoryConfigurer configurer) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + configurer.configure(factory, connectionFactory); + return factory; + } + } + + - type: org.springframework.sbm.jee.jms.actions.ReplaceMdbAnnotationWithJmsListener + condition: + type: org.springframework.sbm.java.migration.conditions.HasTypeAnnotation + annotation: 'javax.ejb.MessageDriven' + description: Replace EJB MessageDriven annotation with Spring JmsListener annotation. + diff --git a/components/jaxrs-recipes/src/main/resources/recipes/migrate-jndi-lookup.yaml b/components/jaxrs-recipes/src/main/resources/recipes/migrate-jndi-lookup.yaml new file mode 100644 index 000000000..39f8d1ffc --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/migrate-jndi-lookup.yaml @@ -0,0 +1,16 @@ +- name: migrate-jndi-lookup + description: Migrate JNDI lookup using InitialContext to Spring Boot + order: 92 + condition: + + description: Any class has import javax.naming.InitialContext + type: org.springframework.sbm.java.migration.conditions.HasImportStartingWith + value: javax.naming.InitialContext + + actions: + - type: org.springframework.sbm.jee.ejb.actions.MigrateJndiLookup + condition: + description: Any class has import javax.naming.InitialContext + type: org.springframework.sbm.java.migration.conditions.HasImportStartingWith + value: javax.naming.InitialContext + description: Migrate JNDI lookup using InitialContext to Spring Boot \ No newline at end of file diff --git a/components/jaxrs-recipes/src/main/resources/recipes/migrate-jpa-to-spring-boot.yaml b/components/jaxrs-recipes/src/main/resources/recipes/migrate-jpa-to-spring-boot.yaml new file mode 100644 index 000000000..bf9413163 --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/migrate-jpa-to-spring-boot.yaml @@ -0,0 +1,96 @@ +- name: migrate-jpa-to-spring-boot + description: Migrate JPA to Spring Boot + order: 90 + condition: + type: org.springframework.sbm.common.migration.conditions.FileExist + fileName: persistence.xml + + actions: + - type: org.springframework.sbm.jee.jpa.actions.MigratePersistenceXmlToApplicationPropertiesAction + condition: + description: Any class has import starting with javax.persistence + type: org.springframework.sbm.java.migration.conditions.HasImportStartingWith + value: javax.persistence + name: Migrate JPA to Spring Boot + description: Move JPA related properties from persistence.xml to application.properties. + detailedDescription: |- + Settings from persistence.xml will be migrated to Spring Boot application.properties + If a datasource is looked up using JNDI manual intervention is required. + + - type: org.springframework.sbm.build.migration.actions.AddDependencies + condition: + type: org.springframework.sbm.build.migration.conditions.NoDependencyExistMatchingRegex + dependencies: + - 'org\.springframework\.boot\:spring-boot-starter-data-jpa\:.*' + dependencies: + - groupId: org.springframework.boot + artifactId: spring-boot-starter-data-jpa + version: 2.6.3 + description: Add spring-boot-starter-data-jpa to build file. + detailedDescription: |- + This code snippet is going to be added to the section in pom.xml + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + - type: org.springframework.sbm.build.migration.actions.AddDependencies + condition: + type: org.springframework.sbm.build.migration.conditions.NoDependencyExistMatchingRegex + dependencies: + - 'com.h2database:h2' + dependencies: + - groupId: com.h2database + artifactId: h2 + version: 2.1.210 + scope: runtime + description: Add h2 dependency to build file. + detailedDescription: |- + This code snippet is going to be added to the section in pom.xml + + + com.h2database + h2 + runtime + + + - type: org.springframework.sbm.build.migration.actions.RemoveDependenciesMatchingRegex + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + dependenciesRegex: + - org\.hibernate\:(hibernate-core|hibernate-entitymanager|hibernate|hibernate-hikaricp|ejb3-persistence)\:.* + - org\.hibernate\.javax\.persistence\:hibernate-jpa-2\.1-api\:.* + - org\.apache\.tomee\:openejb-core-hibernate\:.* + description: Delete hibernate and dependencies transitively managed by spring-boot-starter-data-jpa. + detailedDescription: |- + This action will delete explicit 'hibernate' and related dependencies from a build file + as they are transitively managed by 'spring-boot-starter-data-jpa' dependency. + + - type: org.springframework.sbm.java.migration.actions.AddTypeAnnotationToTypeAnnotatedWith + condition: + type: org.springframework.sbm.jee.ejb.conditions.NoTransactionalAnnotationPresentOnTypeAnnotatedWith + typeAnnotatedWith: org.springframework.stereotype.Service + description: Add @Transactional to @Service as replacement for CMT. + annotation: org.springframework.transaction.annotation.Transactional + annotatedWith: org.springframework.stereotype.Service + + - type: org.springframework.sbm.jee.jpa.actions.MigrateEclipseLinkToSpringBoot + condition: + type: org.springframework.sbm.build.migration.conditions.AnyDeclaredDependencyExistMatchingRegex + dependencies: + - 'org\.eclipse\.persistence\:eclipselink\:.*' + description: Adds configuration for eclipselink, migrates properties from persistence.xml and ignores hibernate dependencies. + + - type: org.springframework.sbm.common.migration.actions.DeleteFileMatchingPattern + description: Deletes all persistence.xml files + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + pattern: '/**/persistence.xml' + + - type: org.springframework.sbm.jee.jpa.actions.RenameUnitNameOfPersistenceContextAnnotationsToDefault + description: Set 'unitName' attribute from @PersistenceContext annotations to 'default' when different + condition: + type: org.springframework.sbm.java.migration.conditions.HasMemberAnnotation + annotation: javax.persistence.PersistenceContext diff --git a/components/jaxrs-recipes/src/main/resources/recipes/migrate-jsf-2.x-to-spring-boot.yaml b/components/jaxrs-recipes/src/main/resources/recipes/migrate-jsf-2.x-to-spring-boot.yaml new file mode 100644 index 000000000..95c543e68 --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/migrate-jsf-2.x-to-spring-boot.yaml @@ -0,0 +1,99 @@ +# Find JSF implementation +- name: migrate-jsf-2.x-to-spring-boot + description: Use joinfaces to integrate JSF 2.x with Spring Boot. + condition: + type: org.springframework.sbm.jee.jsf.conditions.IsMigrateJsf2ToSpringBootApplicableCondition + + actions: + # Add joinfaces plugin definition + - type: org.springframework.sbm.build.migration.actions.AddMavenPlugin + plugin: + groupId: org.joinfaces + artifactId: joinfaces-maven-plugin + version: 4.4.2 + executions: + - goals: + - classpath-scan + condition: + type: org.springframework.sbm.build.migration.conditions.MavenPluginDoesNotExist + plugin: + groupId: org.joinfaces + artifactId: joinfaces-maven-plugin + description: Add joinfaces plugin definition to pom.xml. + detailedDescription: |2 + Add joinfaces plugin definition to pom.xml + + org.joinfaces + joinfaces-maven-plugin + + + + classpath-scan + + + + + + # Delete conflicting spring-boot-starter + - type: org.springframework.sbm.build.migration.actions.RemoveDependenciesMatchingRegex + description: Remove Spring Boot starter conflicting with joinfaces. + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + dependenciesRegex: + - org\.springframework\.boot\:(spring-boot-starter-web|spring-boot-starter)\:.* + + # Calculate and add joinfaces dependencies + - type: org.springframework.sbm.jee.jsf.actions.AddJoinfacesDependencies + condition: + type: org.springframework.sbm.build.migration.conditions.NoExactDependencyExist + dependency: + groupId: org.joinfaces + artifactId: jsf-spring-boot-starter + description: Add the required joinfaces dependencies + + # Remove old/previous JSF dependencies + - type: org.springframework.sbm.build.migration.actions.RemoveDependenciesMatchingRegex + description: Remove JSF dependencies provided by joinfaces. + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + dependenciesRegex: + - javax\.faces\:(javax\.faces-api|jsf-api)\:.* + - org\.apache\.myfaces\.core\:(myfaces-api|myfaces-impl)\:.* + - com\.sun\.faces\:(jsf-api|jsf-impl)\:.* + - org\.jboss\.spec\.javax\.faces\:jboss-jsf-api_2.* + - org\.glassfish\:javax\.faces.* + + # Move faces-config.xml + - type: org.springframework.sbm.common.migration.actions.MoveFilesAction + description: Move faces-config.xml to src/main/resources/META-INF/. + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + fromPattern: /**/faces-config.xml + toDir: ./src/main/resources/META-INF/ + + # Move XHTML pages + - type: org.springframework.sbm.common.migration.actions.MoveFilesAction + description: Move *.xhtml files to src/main/resources/META-INF/resources. + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + fromPattern: /**/*.xhtml + toDir: ./src/main/resources/META-INF/resources + + # Move jsp pages + - type: org.springframework.sbm.common.migration.actions.MoveFilesAction + description: Move *.jsp files to src/main/resources/META-INF/resources. + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + fromPattern: /**/*.jsp + toDir: ./src/main/resources/META-INF/resources + + # Move javascipt files + - type: org.springframework.sbm.common.migration.actions.MoveFilesAction + description: Move *.js files to src/main/resources/META-INF/resources. + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + fromPattern: /**/*.jsp + toDir: ./src/main/resources/META-INF/resources + # Remove or comment out JSF relevant bits from web.xml + + # Maybe remove web.xml diff --git a/components/jaxrs-recipes/src/main/resources/recipes/migrate-stateless-ejb.yaml b/components/jaxrs-recipes/src/main/resources/recipes/migrate-stateless-ejb.yaml new file mode 100644 index 000000000..598a96c58 --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/migrate-stateless-ejb.yaml @@ -0,0 +1,93 @@ +- name: migrate-stateless-ejb + description: "Migration of stateless EJB to Spring components." + order: 80 + condition: + description: 'Any class has import starting with javax.ejb.Stateless' + type: org.springframework.sbm.java.migration.conditions.HasImportStartingWith + value: javax.ejb.Stateless + + actions: + - type: org.springframework.sbm.jee.ejb.actions.MigrateEjbAnnotations + description: "Replace @EJB injection with Spring Boot @Autowired." + condition: + type: org.springframework.sbm.java.migration.conditions.HasAnnotation + annotation: "javax.ejb.EJB" + + + - type: org.springframework.sbm.java.migration.actions.ReplaceTypeAction + description: "Replace CDI field injection with Spring Boot field injection." + existingType: "javax.inject.Inject" + withType: "org.springframework.beans.factory.annotation.Autowired" + condition: + type: org.springframework.sbm.java.migration.conditions.HasMemberAnnotation + annotation: "javax.inject.Inject" + + + - type: org.springframework.sbm.java.migration.actions.ReplaceTypeAction + existingType: "javax.ejb.Startup" + withType: "org.springframework.stereotype.Service" + condition: + type: org.springframework.sbm.java.migration.conditions.HasTypeAnnotation + annotation: "javax.ejb.Startup" + description: Removes @Startup annotation because in Spring Boot all beans are after startup. + detailedDescription: |- + Removes @Startup annotation because in Spring Boot all beans are by default available after startup of the application context. + If you need to initialize parts of the application before the application context was fully started + you might want to look into e.g. + - ApplicationRunner - https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/ApplicationRunner.html + - init method of @Bean - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html + - listening for ContextRefreshedEvents by defining a method annotated with @EventListener + - listening for ContextRefreshedEvents by implementing ApplicationListener. + + + + - type: org.springframework.sbm.jee.ejb.actions.MigrateLocalStatelessSessionBeans + condition: + type: org.springframework.sbm.java.migration.conditions.HasTypeAnnotation + annotation: "javax.ejb.Stateless" + description: "Migrate local @Stateless, @LocalBean and @Singleton to Spring Boot Service" + + + - type: org.springframework.sbm.build.migration.actions.RemoveDependenciesMatchingRegex + dependenciesRegex: + - javax\:javaee-api.* + - javax\.ejb\:javax\.ejb-api\:.* + - org\.jboss\.spec\.javax\.ejb\:jboss-ejb-api_3.* + condition: + type: org.springframework.sbm.common.migration.conditions.TrueCondition + description: Delete ejb dependencies from build file. + + + - type: org.springframework.sbm.build.migration.actions.AddDependencies + condition: + type: org.springframework.sbm.build.migration.conditions.NoDependencyExistMatchingRegex + dependencies: + - 'org.springframework.boot:spring-boot-starter-data-jpa' + dependencies: + - groupId: org.springframework.boot + artifactId: spring-boot-starter-data-jpa + version: latest.release + description: Add spring-boot-starter-data-jpa to build file. + detailedDescription: |- + This code snippet is going to be added to the section in pom.xml + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + - type: org.springframework.sbm.java.migration.actions.AddTypeAnnotationToTypeAnnotatedWith + condition: + type: org.springframework.sbm.jee.ejb.conditions.NoTransactionalAnnotationPresentOnTypeAnnotatedWith + typeAnnotatedWith: org.springframework.stereotype.Service + description: Add @Transactional to @Service as replacement for CMT. + annotation: org.springframework.transaction.annotation.Transactional + annotatedWith: org.springframework.stereotype.Service + addAnnotationIfExists: false + +# - name: "delete-class-annotations" +# description: "Refactor (local) EJBs annotated with @Stateless and only @Local annotation with Spring @Component annotation" +# annotations: +# - "javax.ejb.Local" +# - "javax.ejb.Startup" diff --git a/components/jaxrs-recipes/src/main/resources/recipes/migrate-tx-to-spring-boot.yaml b/components/jaxrs-recipes/src/main/resources/recipes/migrate-tx-to-spring-boot.yaml new file mode 100644 index 000000000..fd1dae7c7 --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/recipes/migrate-tx-to-spring-boot.yaml @@ -0,0 +1,18 @@ +- name: migrate-tx-to-spring-boot + description: Migration of @TransactionAttribute to @Transactionsl + order: 50 + condition: + type: org.springframework.sbm.java.migration.conditions.HasImportStartingWith + value: javax.ejb.TransactionAttribute + + actions: + - type: org.springframework.sbm.jee.tx.actions.MigrateJeeTransactionsToSpringBootAction + condition: + type: org.springframework.sbm.java.migration.conditions.HasAnyTypeReference + fqTypeNames: + - javax.ejb.TransactionAttribute + - javax.ejb.TransactionAttributeType + - javax.ejb.TransactionManagement + - javax.ejb.TransactionManagementType + description: |- + Removes @TransactionManagement and replaces @TransactionAttribute with @Transactional diff --git a/components/jaxrs-recipes/src/main/resources/templates/eclipselink-configuration-class.ftl b/components/jaxrs-recipes/src/main/resources/templates/eclipselink-configuration-class.ftl new file mode 100644 index 000000000..5cb257775 --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/templates/eclipselink-configuration-class.ftl @@ -0,0 +1,51 @@ +<#if packageName?has_content> +package ${packageName}; + + +import org.eclipse.persistence.config.PersistenceUnitProperties; +import org.eclipse.persistence.logging.SessionLog; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableLoadTimeWeaving; +import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver; +import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; +import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter; +import org.springframework.transaction.jta.JtaTransactionManager; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +@Configuration +@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED) +public class ${className} extends JpaBaseConfiguration { + + protected EclipseLinkJpaConfiguration(DataSource dataSource, JpaProperties properties, ObjectProvider jtaTransactionManager) { + super(dataSource, properties, jtaTransactionManager); + } + + @Override + protected AbstractJpaVendorAdapter createJpaVendorAdapter() { + return new EclipseLinkJpaVendorAdapter(); + } + + @Override + protected Map getVendorProperties() { + Map map = new HashMap<>(); + + <#list eclipseLinkProperties?keys as p> + map.put(${p}, "${eclipseLinkProperties[p]}"); + + + return map; + } + + @Bean + public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable { + InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver(); + return loadTimeWeaver; + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/main/resources/templates/jaxws-endpoint.ftl b/components/jaxrs-recipes/src/main/resources/templates/jaxws-endpoint.ftl new file mode 100644 index 000000000..f2cb4876c --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/templates/jaxws-endpoint.ftl @@ -0,0 +1,26 @@ +<#if packageName?has_content> + package ${packageName}; + + +import org.springframework.ws.server.endpoint.annotation.Endpoint; + +import ${serviceFqName}; +<#list imports as imp> + import ${imp}; + + +@Endpoint +public class ${className} { + +private static final String ${nsUriConstantName} = "${nsUri}"; + +private ${serviceSimpleName} ${serviceFieldName}; + +${className}(${serviceSimpleName} ${serviceFieldName}) { +this.${serviceFieldName} = ${serviceFieldName}; +} + +<#list operations as op> + ${op} + +} diff --git a/components/jaxrs-recipes/src/main/resources/templates/jaxws-web-config.ftl b/components/jaxrs-recipes/src/main/resources/templates/jaxws-web-config.ftl new file mode 100644 index 000000000..a4b1b5ad2 --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/templates/jaxws-web-config.ftl @@ -0,0 +1,38 @@ +<#if packageName?has_content> + package ${packageName}; + + +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.ws.config.annotation.EnableWs; +import org.springframework.ws.config.annotation.WsConfigurerAdapter; +import org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition; +import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition; +import org.springframework.ws.transport.http.MessageDispatcherServlet; + +@EnableWs +@Configuration +public class ${className} extends WsConfigurerAdapter { + +@Bean +public ServletRegistrationBean + messageDispatcherServlet(ApplicationContext applicationContext) { + MessageDispatcherServlet servlet = new MessageDispatcherServlet(); + servlet.setApplicationContext(applicationContext); + servlet.setTransformWsdlLocations(true); + return new ServletRegistrationBean<>(servlet, <#list wsdls as wsdl>"/${wsdl.contextPath}/*"<#sep>, ); + } + + <#list wsdls as wsdl> + @Bean(name = "${wsdl.beanName}") + SimpleWsdl11Definition ${wsdl.methodName}() { + SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition(); + wsdl11Definition.setWsdl(new ClassPathResource("${wsdl.file}")); + return wsdl11Definition; + } + + + } diff --git a/components/jaxrs-recipes/src/main/resources/templates/jms-config.ftl b/components/jaxrs-recipes/src/main/resources/templates/jms-config.ftl new file mode 100644 index 000000000..446bddf9f --- /dev/null +++ b/components/jaxrs-recipes/src/main/resources/templates/jms-config.ftl @@ -0,0 +1,41 @@ +<#if packageName?has_content> +package ${packageName}; + + +import javax.jms.ConnectionFactory; + +<#if queues?has_content> +import javax.jms.Queue; +import org.apache.activemq.command.ActiveMQQueue; +import javax.jms.JMSException; + + +import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.config.JmsListenerContainerFactory; + +@Configuration +@EnableJms +public class ${className} { + + @Bean + public JmsListenerContainerFactory jmsListenerContainerFactory( + ConnectionFactory connectionFactory, + DefaultJmsListenerContainerFactoryConfigurer configurer) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + configurer.configure(factory, connectionFactory); + return factory; + } + +<#list queues as key, value> + @Bean + Queue ${key}(ConnectionFactory connectionFactory) throws JMSException { + ActiveMQQueue activeMQQueue = new ActiveMQQueue(${value}); + return activeMQQueue; + } + + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/AddNewDependencyTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/AddNewDependencyTest.java new file mode 100644 index 000000000..8a3d4ec91 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/AddNewDependencyTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 - 2023 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; + +import org.springframework.sbm.build.api.BuildFile; +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.Type; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestMethodOrder(MethodOrderer.MethodName.class) +public class AddNewDependencyTest { + + + @Test + @Disabled + void t1_typesNotResolvedWhenDependencyIsMissing() { + String javaSourceCode = "public class AnnotateMePlease {}"; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(javaSourceCode) + .build(); + + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + Type type = javaSource.getTypes().get(0); +// buildFile.addDependency(dependencyToAdd); + type.addAnnotation("javax.ejb.Stateless"); + assertThat(javaSource.print()).isEqualTo( + "@Stateless\n" + + "public class AnnotateMePlease {}" + ); + assertThat(type.hasAnnotation("javax.ejb.Stateless")).isFalse(); + } + + // FIXME: flaky tests, succeed one by one, fail depending on the order when executed together + // in FindTypes ident.getType() is not null when t1_typesNotResolvedWhenDependencyIsMissing runs last + @Test + @Disabled + void t1_typesShouldBeResolvedWhenDependencyIsAdded() { + String javaSourceCode = "public class AnnotateMePlease {}"; + Dependency dependencyToAdd = Dependency.builder() + .groupId("javax.ejb") + .artifactId("javax.ejb-api") + .version("3.2") + .build(); + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(javaSourceCode) + .build(); + + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + Type type = javaSource.getTypes().get(0); + + BuildFile buildFile = projectContext.getBuildFile(); + + buildFile.addDependency(dependencyToAdd); + + type.addAnnotation("javax.ejb.Stateless"); + assertThat(javaSource.print()).isEqualTo( + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class AnnotateMePlease {}" + ); + assertThat(type.hasAnnotation("javax.ejb.Stateless")).isTrue(); + } + + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/architecture/ControlledInstantiationOfExecutionContextTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/architecture/ControlledInstantiationOfExecutionContextTest.java new file mode 100644 index 000000000..cc7fb88ea --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/architecture/ControlledInstantiationOfExecutionContextTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021 - 2023 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.architecture; + +import com.tngtech.archunit.core.domain.AccessTarget; +import com.tngtech.archunit.core.domain.JavaCall; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; +import org.openrewrite.ExecutionContext; +import org.springframework.rewrite.boot.autoconfigure.ScopeConfiguration; +import org.springframework.rewrite.parser.RewriteExecutionContext; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +@AnalyzeClasses(packages = {"org.springframework.sbm", "org.openrewrite"}, importOptions = {ImportOption.DoNotIncludeTests.class, ImportOption.DoNotIncludeJars.class}) +public class ControlledInstantiationOfExecutionContextTest { + + private static final Class classWithPermissionToCreateExecutionContext = ScopeConfiguration.class; + + @ArchTest + public static final ArchRule noClassInstantiatesExecutionContextWillyNilly = + noClasses() + .should() + .callCodeUnitWhere( + JavaCall.Predicates.target( + AccessTarget.Predicates.constructor() + .and(AccessTarget.Predicates.declaredIn( + JavaClass.Predicates.assignableTo(ExecutionContext.class) + )) + ) + ) + .andShould() + .notBe(classWithPermissionToCreateExecutionContext) + .andShould() + .notBe(RewriteExecutionContext.class) + ; +} + diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbAnnotationsTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbAnnotationsTest.java new file mode 100644 index 000000000..84c93891b --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbAnnotationsTest.java @@ -0,0 +1,354 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.GitHubIssue; +import org.springframework.sbm.test.JavaMigrationActionTestSupport; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class MigrateEjbAnnotationsTest { + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/204") + void replaceSimpleEjbOnMember() { + List given = List.of( + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SomeEjb {}", + + "import javax.ejb.Stateless;\n" + + "import javax.ejb.EJB;\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " @EJB\n" + + " private SomeEjb someEjb;\n" + + "}" + ); + List expected = List.of( + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SomeEjb {}", + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " @Autowired\n" + + " private SomeEjb someEjb;\n" + + "}" + ); + + MigrateEjbAnnotations sut = new MigrateEjbAnnotations(); + + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/204") + void replaceEjbWithBeanNameOnMember() { + List given = List.of( + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SomeEjb {}", + + "import javax.ejb.Stateless;\n" + + "import javax.ejb.EJB;\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " @EJB(beanName = \"someEjbBeanName\")\n" + + " private SomeEjb someEjb;\n" + + "}" + ); + List expected = List.of( + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SomeEjb {}", + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "import org.springframework.beans.factory.annotation.Qualifier;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " @Autowired\n" + + " @Qualifier(\"someEjbBeanName\")\n" + + " private SomeEjb someEjb;\n" + + "}" + ); + + MigrateEjbAnnotations sut = new MigrateEjbAnnotations(); + + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/204") + void replaceSimpleEjbOnMemberAndField() { + List given = List.of( + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SomeEjb {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class AnotherEjb {}", + + "import javax.ejb.Stateless;\n" + + "import javax.ejb.EJB;\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " @EJB\n" + + " private SomeEjb someEjb;\n" + + " private AnotherEjb anotherEjb;\n" + + " @EJB\n" + + " public void setAnotherEjb(AnotherEjb anotherEjb) {\n" + + " this.anotherEjb = anotherEjb;\n" + + " }\n" + + "}" + ); + List expected = List.of( + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SomeEjb {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class AnotherEjb {}", + + "import javax.ejb.Stateless;\n" + + "\n" + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " @Autowired\n" + + " private SomeEjb someEjb;\n" + + " private AnotherEjb anotherEjb;\n" + + "\n" + + " @Autowired\n" + + " public void setAnotherEjb(AnotherEjb anotherEjb) {\n" + + " this.anotherEjb = anotherEjb;\n" + + " }\n" + + "}" + ); + + MigrateEjbAnnotations sut = new MigrateEjbAnnotations(); + + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/204") + void replaceEjbWithBeanNameOnField() { + List given = List.of( + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"fancyEjb\")\n" + + "public class AnotherEjb {}", + + "import javax.ejb.Stateless;\n" + + "import javax.ejb.EJB;\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " private AnotherEjb anotherEjb;\n" + + " @EJB(beanName=\"fancyEjb\")\n" + + " public void setAnotherEjb(AnotherEjb anotherEjb) {\n" + + " this.anotherEjb = anotherEjb;\n" + + " }\n" + + "}" + ); + List expected = List.of( + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"fancyEjb\")\n" + + "public class AnotherEjb {}", + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "import org.springframework.beans.factory.annotation.Qualifier;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " private AnotherEjb anotherEjb;\n" + + "\n" + + " @Autowired\n" + + " @Qualifier(\"fancyEjb\")\n" + + " public void setAnotherEjb(AnotherEjb anotherEjb) {\n" + + " this.anotherEjb = anotherEjb;\n" + + " }\n" + + "}" + ); + + MigrateEjbAnnotations sut = new MigrateEjbAnnotations(); + + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/204") + // TODO: beanInterface gets not migrated, see #219 + void allAtOnce() { + List given = List.of( + "public interface Another {}", + + "import javax.ejb.Local;\n" + + "@Local\n" + + "public interface LocalAnother extends Another {}", + + "import javax.ejb.Remote;\n" + + "@Remote\n" + + "public interface RemoteAnother extends Another {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class TheEjb {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"fancyEjb\")\n" + + "public class AnotherEjb implements LocalAnother, RemoteAnother {}", + + "import javax.ejb.Stateless;\n" + + "import javax.ejb.EJB;\n" + + "@Stateless\n" + + "public class ClientEjb {\n" + + " @EJB(description = \"the description for theEJB\", lookup = \"ejb:earname/modulename/TheEJB!TheEJB\")\n" + + " private TheEjb theEjb;\n" + + " private Another anotherEjb;\n" + + " @EJB(" + + " beanName=\"fancyEjb\", " + + " description = \"the description\", " + + " beanInterface = LocalAnother.class, " + + " mappedName = \"fancyMappedName\"," + + " name = \"theName\")\n" + + " public void setAnotherEjb(Another anotherEjb) {\n" + + " this.anotherEjb = anotherEjb;\n" + + " }\n" + + "}" + ); + + List expected = List.of( + "public interface Another {}", + + "import javax.ejb.Local;\n" + + "@Local\n" + + "public interface LocalAnother extends Another {}", + + "import javax.ejb.Remote;\n" + + "@Remote\n" + + "public interface RemoteAnother extends Another {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class TheEjb {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"fancyEjb\")\n" + + "public class AnotherEjb implements LocalAnother, RemoteAnother {}", + + "import javax.ejb.Stateless;\n" + + "\n" + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "import org.springframework.beans.factory.annotation.Qualifier;\n" + + "\n" + + + "@Stateless\n" + + "public class ClientEjb {\n" + + " /*\n" + + " * the description for theEJB\n" + + " * SBM-TODO: lookup was 'ejb:earname/modulename/TheEJB!TheEJB'\n" + + " */\n" + + " @Autowired\n" + + " private TheEjb theEjb;\n" + + " private Another anotherEjb;\n" + + "\n" + + " /*\n" + + " * the description\n" + + " * SBM-TODO: beanInterface was 'LocalAnother.class'\n" + + " */\n" + + " @Autowired\n" + + " @Qualifier(\"fancyEjb\")\n" + + " public void setAnotherEjb(Another anotherEjb) {\n" + + " this.anotherEjb = anotherEjb;\n" + + " }\n" + + "}"); + + MigrateEjbAnnotations sut = new MigrateEjbAnnotations(); + + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/159") + void test() { + List given = List.of( + "public interface BusinessInterface {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"TheBeanA\")\n" + + "public class BeanA implements BusinessInterface {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"TheBeanB\")\n" + + "public class BeanB implements BusinessInterface {}", + + "import javax.ejb.Stateless;\n" + + "import javax.ejb.EJB;\n" + + "@Stateless\n" + + "public class Client {\n" + + " @EJB(beanName=\"TheBeanA\")\n" + + " private BusinessInterface a;\n" + + " @EJB(beanName=\"TheBeanB\")\n" + + " private BusinessInterface b;\n" + + "}" + ); + + List expected = List.of( + "public interface BusinessInterface {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"TheBeanA\")\n" + + "public class BeanA implements BusinessInterface {}", + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"TheBeanB\")\n" + + "public class BeanB implements BusinessInterface {}", + + "import javax.ejb.Stateless;\n" + + "\n" + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "import org.springframework.beans.factory.annotation.Qualifier;\n" + + "\n" + + "@Stateless\n" + + "public class Client {\n" + + " @Autowired\n" + + " @Qualifier(\"TheBeanA\")\n" + + " private BusinessInterface a;\n" + + " @Autowired\n" + + " @Qualifier(\"TheBeanB\")\n" + + " private BusinessInterface b;\n" + + "}" + ); + + MigrateEjbAnnotations sut = new MigrateEjbAnnotations(); + + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java new file mode 100644 index 000000000..67c2d0bab --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java @@ -0,0 +1,243 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.jee.ejb.api.EjbJarXml; +import org.springframework.sbm.jee.ejb.resource.JeeEjbJarXmlProjectResourceRegistrar; +import org.springframework.rewrite.resource.finder.GenericTypeListFinder; +import org.springframework.sbm.project.resource.TestProjectContext; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class MigrateEjbDeploymentDescriptorTest { + + public static final String EJB_CLASS_FQNAME = "com.example.jee.ejb.stateless.local.deploymentdescriptor.NoInterfaceViewBean"; + public static final String EJB_TYPE = "Stateless"; + private static final String EJB_NAME = "noInterfaceView"; + private static final String EJB_WITH_MAPPED_NAME = "MappedNameView"; + private static final String EJB_WITH_MAPPED_CLASS_FQNAME = "com.example.jee.ejb.stateless.local.deploymentdescriptor.MappedNameView"; + private static final String MAPPED_NAME = "java:comp/env/ejb/MappedNameViewBean"; + private static final String EJB_WITH_REMOTE_INTERFACE_NAME = "RemoteInterfaceView"; + public static final String EJB_WITH_REMOTE_INTERFACE_FQDN = "com.example.jee.ejb.stateless.local.deploymentdescriptor.RemoteInterfaceView"; + private static final String REMOTE_EJB_INTERFACE = "com.example.jee.ejb.stateless.local.deploymentdescriptor.RemoteInterface"; + private static final String EJB_WITH_LOCAL_INTERFACE_NAME = "LocalInterfaceView"; + public static final String EJB_WITH_LOCAL_INTERFACE_FQDN = "com.example.jee.ejb.stateless.local.deploymentdescriptor.LocalInterfaceView"; + private static final String LOCAL_EJB_INTERFACE = "com.example.jee.ejb.stateless.local.deploymentdescriptor.LocalInterface"; + + @Test + void givenDeploymentDescriptorContainsEjbWhenMatchingClassIsFoundThenStatelessAnnotationShouldBeOverwritten() { + // setup fixture + String javaSource = "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"banana\")\n" + + "public class NoInterfaceViewBean {}"; + + String expected = "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless(name = \"" + EJB_NAME + "\")\n" + + "public class NoInterfaceViewBean {}"; + + String deploymentDescriptorXml = "\n" + + " \n" + + " \n" + + " " + EJB_NAME + "\n" + + " " + EJB_CLASS_FQNAME + "\n" + + " " + EJB_TYPE + "\n" + + " \n" + + " \n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withProjectResource(Path.of("./src/main/resources/META-INF/ejb-jar.xml"), deploymentDescriptorXml) + .withJavaSources(javaSource) + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2") + .addRegistrar(new JeeEjbJarXmlProjectResourceRegistrar()) + .build(); + + + // call SUT + MigrateEjbDeploymentDescriptor sut = new MigrateEjbDeploymentDescriptor(); + sut.apply(projectContext); + + // verify... + assertThat(projectContext.getProjectJavaSources().list().size()).isEqualTo(1); + assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); + List deploymentDescriptors = projectContext.search(new GenericTypeListFinder<>(EjbJarXml.class)); + assertThat(deploymentDescriptors).isEmpty(); + } + + @Test + void givenDeploymentDescriptorContainsEjbWithMappedName_whenMatchingClassIsFound_thenStatelessAnnotationShouldBeOverwritten() { + // setup fixture + String javaSource = + "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"banana\")\n" + + "public class MappedNameView {}"; + + String expected = "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless(name = \"" + EJB_WITH_MAPPED_NAME + "\", mappedName = \"" + MAPPED_NAME + "\")\n" + + "public class MappedNameView {}"; + + String deploymentDescriptorXml = "\n" + + " \n" + + " \n" + + " " + EJB_WITH_MAPPED_NAME + "\n" + + " " + EJB_WITH_MAPPED_CLASS_FQNAME + "\n" + + " " + MAPPED_NAME + "\n" + + " " + EJB_TYPE + "\n" + + " \n" + + " \n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withProjectResource(Path.of("./src/main/resources/META-INF/ejb-jar.xml"), deploymentDescriptorXml) + .withJavaSources(javaSource) + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2") + .addRegistrar(new JeeEjbJarXmlProjectResourceRegistrar()) + .build(); + + + // call SUT + MigrateEjbDeploymentDescriptor sut = new MigrateEjbDeploymentDescriptor(); + sut.apply(projectContext); + + // verify... + assertThat(projectContext.getProjectJavaSources().list().size()).isEqualTo(1); + assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); + List deploymentDescriptors = projectContext.search(new GenericTypeListFinder<>(EjbJarXml.class)); + assertThat(deploymentDescriptors).isEmpty(); + } + + @Test + void givenDeploymentDescriptorContainsEjbWithRemoteInterface_whenMatchingClassIsFound_thenStatelessRemoteAnnotationShouldBeGenerated() { + // setup fixture + String javaSource = + "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + + "import javax.ejb.Stateless;\n" + + "public class RemoteInterfaceView implements RemoteInterface{}"; + + String expected = "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + + "import javax.ejb.Remote;\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless(name = \"" + EJB_WITH_REMOTE_INTERFACE_NAME + "\")\n" + + "@Remote(" + REMOTE_EJB_INTERFACE + ".class)\n" + + "public class RemoteInterfaceView implements RemoteInterface {}"; + + String deploymentDescriptorXml = "\n" + + " \n" + + " \n" + + " " + EJB_WITH_REMOTE_INTERFACE_NAME + "\n" + + " " + EJB_WITH_REMOTE_INTERFACE_FQDN + "\n" + + " " + REMOTE_EJB_INTERFACE + "\n" + + " " + EJB_TYPE + "\n" + + " \n" + + " \n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withProjectResource(Path.of("./src/main/resources/META-INF/ejb-jar.xml"), deploymentDescriptorXml) + .withJavaSources(javaSource) + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2") + .addRegistrar(new JeeEjbJarXmlProjectResourceRegistrar()) + .build(); + + + // call SUT + MigrateEjbDeploymentDescriptor sut = new MigrateEjbDeploymentDescriptor(); + sut.apply(projectContext); + + // verify... + assertThat(projectContext.getProjectJavaSources().list().size()).isEqualTo(1); + assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); + List deploymentDescriptors = projectContext.search(new GenericTypeListFinder<>(EjbJarXml.class)); + assertThat(deploymentDescriptors).isEmpty(); + } + + @Test + void givenDeploymentDescriptorContainsEjbWithLocalInterface_whenMatchingClassIsFound_thenStatelessLocalAnnotationShouldBeGenerated() { + // setup fixture + String javaSource = + "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + + "import javax.ejb.Stateless;\n" + + "public class LocalInterfaceView implements LocalInterface{}"; + + String expected = "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + + "import javax.ejb.Local;\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless(name = \"" + EJB_WITH_LOCAL_INTERFACE_NAME + "\")\n" + + "@Local(" + LOCAL_EJB_INTERFACE + ".class)\n" + + "public class LocalInterfaceView implements LocalInterface {}"; + + String deploymentDescriptorXml = "\n" + + " \n" + + " \n" + + " " + EJB_WITH_LOCAL_INTERFACE_NAME + "\n" + + " " + EJB_WITH_LOCAL_INTERFACE_FQDN + "\n" + + " " + LOCAL_EJB_INTERFACE + "\n" + + " " + EJB_TYPE + "\n" + + " \n" + + " \n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withProjectResource(Path.of("./src/main/resources/META-INF/ejb-jar.xml"), deploymentDescriptorXml) + .withJavaSources(javaSource) + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2") + .addRegistrar(new JeeEjbJarXmlProjectResourceRegistrar()) + .build(); + + + // call SUT + MigrateEjbDeploymentDescriptor sut = new MigrateEjbDeploymentDescriptor(); + sut.apply(projectContext); + + // verify... + assertThat(projectContext.getProjectJavaSources().list().size()).isEqualTo(1); + assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); + List deploymentDescriptors = projectContext.search(new GenericTypeListFinder<>(EjbJarXml.class)); + assertThat(deploymentDescriptors).isEmpty(); + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookupTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookupTest.java new file mode 100644 index 000000000..fde0a9391 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookupTest.java @@ -0,0 +1,264 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.GitHubIssue; +import org.springframework.sbm.test.JavaMigrationActionTestSupport; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class MigrateJndiLookupTest { + + @Test + @Disabled("Migrate JNDI lookup needs refactoring. Test is affected by concurrency settings in Rewrite visitors through Recipe.run(...)") + void memberLookupWithCast() { + List given = List.of( + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import javax.naming.InitialContext;\n" + + "import javax.naming.NamingException;\n" + + "\n" + + "@Stateless\n" + + "public class BeanWithJndiMemberLookup {\n" + + "\n" + + " private BeanWithJndiName example = (BeanWithJndiName) InitialContext.doLookup(\"java:module/beanWithJndiName\");\n" + + "\n" + + " public BeanWithJndiMemberLookup() throws NamingException {\n" + + " }\n" + + "}", + // ----------- + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class BeanWithJndiName {\n" + + "}\n" + ); + + List expected = List.of( + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import javax.naming.NamingException;\n" + + "\n" + + "@Stateless\n" + + "public class BeanWithJndiMemberLookup {\n" + + " @Autowired\n" + + " private BeanWithJndiName example;\n" + + "\n" + + // FIXME: find and remove obsolete throws clause + " public BeanWithJndiMemberLookup() throws NamingException {\n" + + " }\n" + + "}", + // ----------- + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class BeanWithJndiName {\n" + + "}\n" + ); + MigrateJndiLookup sut = new MigrateJndiLookup(); + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + + @Test + @Disabled("Migrate JNDI lookup needs refactoring. Test is affected by concurrency settings in Rewrite visitors through Recipe.run(...)") + void memberLookupWithoutCast() { + List given = List.of( + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import javax.naming.InitialContext;\n" + + "import javax.naming.NamingException;\n" + + "\n" + + "@Stateless\n" + + "public class BeanWithJndiMemberLookup {\n" + + "\n" + + " private BeanWithJndiName example = InitialContext.doLookup(\"java:module/beanWithJndiName\");\n" + + "\n" + + " public BeanWithJndiMemberLookup() throws NamingException {\n" + + " }\n" + + "}", + // ----------- + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class BeanWithJndiName {\n" + + "}\n" + ); + + List expected = List.of( + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import javax.naming.NamingException;\n" + + "\n" + + "@Stateless\n" + + "public class BeanWithJndiMemberLookup {\n" + + " @Autowired\n" + + " private BeanWithJndiName example;\n" + + "\n" + + " public BeanWithJndiMemberLookup() throws NamingException {\n" + + " }\n" + + "}", + // ----------- + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class BeanWithJndiName {\n" + + "}\n" + ); + MigrateJndiLookup sut = new MigrateJndiLookup(); + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/202") + @Disabled("Migrate JNDI lookup needs refactoring. Test is affected by concurrency settings in Rewrite visitors through Recipe.run(...)") + void test() { + List given = List.of( + "public interface ExampleLocal {public void foo();}", + // ----------- + "import javax.naming.InitialContext;\n" + + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SomeEjb {\n" + + " private ExampleLocal example = (ExampleLocal) InitialContext.doLookup(\"java:module/ExampleLocal\");\n" + + "}" + ); + + List expected = List.of( + "public interface ExampleLocal {public void foo();}", + // ----------- + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class SomeEjb {\n" + + " @Autowired\n" + + " private ExampleLocal example;\n" + + "}" + ); + + MigrateJndiLookup sut = new MigrateJndiLookup(); + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/202") + @Disabled("Migrate JNDI lookup needs refactoring. Test is affected by concurrency settings in Rewrite visitors through Recipe.run(...)") + void test2() { + List given = List.of( + "package com.example.foo; \n" + + "public interface ExampleLocal {public void foo();}", + // ----------- + "package com.example.bar;\n" + + "import javax.naming.InitialContext;\n" + + "import javax.ejb.Stateless;\n" + + "import com.example.foo.ExampleLocal;\n" + + "@Stateless\n" + + "public class SomeEjb {\n" + + " int foo = 3;\n" + + " public void someMethod1() {\n" + + " ExampleLocal example = (ExampleLocal) InitialContext.doLookup(\"java:module/ExampleLocal\");\n" + + " ExampleLocal example2 = (ExampleLocal) InitialContext.doLookup(\"java:module/ExampleLocal\");\n" + + " example.foo();\n" + + " example2.foo();\n" + + " int a = 10;\n" + + " }\n" + + "}" + ); + + List expected = List.of( + "package com.example.foo; \n" + + "public interface ExampleLocal {public void foo();}", + // ----------- + "package com.example.bar;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import com.example.foo.ExampleLocal;\n" + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "@Stateless\n" + + "public class SomeEjb {\n" + +// " // JNDI name was 'java:module/ExampleLocal'" + + " @Autowired\n" + + " private ExampleLocal example2;\n" + + " @Autowired\n" + + " private ExampleLocal example;\n" + + " int foo = 3;\n" + + "\n" + + " public void someMethod1() {\n" + + " example.foo();\n" + + " example2.foo();\n" + +// " int a = 10;\n" + + " }\n" + + "}" + ); + + MigrateJndiLookup sut = new MigrateJndiLookup(); + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-beans:5.3.5"); + } + + @Test + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/202") + @Disabled("Migrate JNDI lookup needs refactoring. Test is affected by concurrency settings in Rewrite visitors through Recipe.run(...)") + void test3() { + List given = List.of( + "public interface ExampleLocal {public void foo();}", + // ----------- + "import javax.naming.InitialContext;\n" + + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SomeEjb {\n" + + " int foo = 3;\n" + + " public void someMethod2() {\n" + + " InitialContext ic = new InitialContext();\n" + + " ExampleLocal example = (ExampleLocal) ic.lookup(\"java:module/ExampleLocal\");\n" + + " example.foo();\n" + + " int a = 10;\n" + + " }\n" + + "}" + ); + + List expected = List.of( + "public interface ExampleLocal {public void foo();}", + // ----------- + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class SomeEjb {\n" + +// " // JNDI name was 'java:module/ExampleLocal'" + + " @Autowired\n" + + " private ExampleLocal example;\n" + + " int foo = 3;\n" + + "\n" + + " public void someMethod2() {\n" + + " example.foo();\n" + + " }\n" + + "}" + ); + + MigrateJndiLookup sut = new MigrateJndiLookup(); + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework:spring-context:5.3.5"); + } + +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansTest.java new file mode 100644 index 000000000..f855a6b3e --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansTest.java @@ -0,0 +1,171 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.GitHubIssue; +import org.springframework.sbm.test.JavaMigrationActionTestSupport; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class MigrateLocalStatelessSessionBeansTest { + + MigrateLocalStatelessSessionBeansHelper helper = new MigrateLocalStatelessSessionBeansHelper(); + MigrateLocalStatelessSessionBeans sut = new MigrateLocalStatelessSessionBeans(helper); + + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/236") + @Test + void migrateLocalBean() { + String givenBean = + "import javax.ejb.*;\n" + + "@Singleton(name = \"DaSingleton\")\n" + + "@LocalBean\n" + + "public class LocalBeanAndLocalAnnotatedBean {}"; + + String expectedBean = + "import org.springframework.stereotype.Service;\n" + + "\n" + + "\n" + + "@Service(\"DaSingleton\")\n" + + "public class LocalBeanAndLocalAnnotatedBean {}"; + + JavaMigrationActionTestSupport.verify( + List.of(givenBean), + List.of(expectedBean), + sut, + "javax.ejb:javax.ejb-api:3.2", + "org.springframework:spring-context:5.3.5"); + } + + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/201") + @Test + void migrateSimpleStatelessAnnotation_201() { + String givenBean = + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"theBeanName\", description=\"Hello, hello\", beanName=\"aFancyBeanName\")\n" + + "public class ImplementingBean2 {}"; + + String expectedBean = + "import org.springframework.stereotype.Service;\n" + + "\n" + + "/**\n" + + "* Hello, hello\n" + + "*/\n" + + "@Service(\"theBeanName\")\n" + + "public class ImplementingBean2 {}"; + + JavaMigrationActionTestSupport.verify( + List.of(givenBean), + List.of(expectedBean), + sut, + "javax.ejb:javax.ejb-api:3.2", + "org.springframework:spring-context:5.3.5"); + } + + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/26") + @Test + void migrateBeanWithLocalStatelessAndInterface_26() { + String givenInterface = + "public interface BusinessInterface {}"; + + String givenBean = + "import javax.ejb.Local;\n" + + "import javax.ejb.Stateless;\n" + + "@Stateless(name=\"theBeanName\", description=\"Hello, hello\")\n" + + "@Local\n" + + "public class ImplementingBean implements BusinessInterface {}"; + + String expectedBean = + "import org.springframework.stereotype.Service;\n" + + "\n" + + "/**\n" + + "* Hello, hello\n" + + "*/\n" + + "@Service(\"theBeanName\")\n" + + "public class ImplementingBean implements BusinessInterface {}"; + + JavaMigrationActionTestSupport.verify( + List.of(givenInterface, givenBean), + List.of(givenInterface, expectedBean), + sut, + "javax.ejb:javax.ejb-api:3.2", + "org.springframework:spring-context:5.3.5" + ); + } + + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/157") + @Test + void migrateEjbWithLocalBusinessInterface_157() { + + String givenBusinessInterface = + "import javax.ejb.Local;\n" + + "@Local\n" + + "public interface BusinessInterface {}"; + + String expectedBusinessInterface = + "public interface BusinessInterface {}"; + + String givenSessionBean = + "import javax.ejb.Stateless;\n" + + "@Stateless\n" + + "public class SessionBean implements BusinessInterface {}"; + + String expectedSessionBean = + "import org.springframework.stereotype.Service;\n" + + "\n" + + "@Service\n" + + "public class SessionBean implements BusinessInterface {}"; + + JavaMigrationActionTestSupport.verify( + List.of(givenBusinessInterface, givenSessionBean), + List.of(expectedBusinessInterface, expectedSessionBean), + sut, + "javax.ejb:javax.ejb-api:3.2", + "org.springframework:spring-context:5.3.5" + ); + } + + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/158") + @Test + void test_158() { + String givenBusinessInterface = + "public interface BusinessInterface {}"; + + String expectedBusinessInterface = + "public interface BusinessInterface {}"; + + String givenSessionBean = + "import javax.ejb.Stateless;\n" + + "import javax.ejb.Local;\n" + + "@Stateless(name = \"beanyBean\")\n" + + "@Local(BusinessInterface.class)\n" + + "public class TheBean implements BusinessInterface {}"; + + String expectedSessionBean = + "import org.springframework.stereotype.Service;\n" + + "\n" + + "@Service(\"beanyBean\")\n" + + "public class TheBean implements BusinessInterface {}"; + + JavaMigrationActionTestSupport.verify( + List.of(givenBusinessInterface, givenSessionBean), + List.of(expectedBusinessInterface, expectedSessionBean), + sut, + "javax.ejb:javax.ejb-api:3.2", + "org.springframework:spring-context:5.3.5" + ); + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/StatelessAnnotationTemplateMapperTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/StatelessAnnotationTemplateMapperTest.java new file mode 100644 index 000000000..d7b577dbd --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/StatelessAnnotationTemplateMapperTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2021 - 2023 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.ejb.actions; + +import org.springframework.sbm.java.api.Annotation; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class StatelessAnnotationTemplateMapperTest { + + private final StatelessAnnotationTemplateMapper sut = new StatelessAnnotationTemplateMapper(); + + // TODO: deal with something like... + /* + * import foo.bar.BeanNames; + * ... + * @Stateless(name=BeanNames.ThisBean) + */ + + @Test + void shouldCreateTemplateStringForStatelessAnnotation_beanName() { + + JavaSource openRewriteJavaSource = TestProjectContext.buildProjectContext() + .withJavaSources( + "import javax.ejb.Stateless;\n" + + "@Stateless(mappedName=\"theMappedName\")\n" + + "public class TheBean {}") + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2") + .build() + .getProjectJavaSources() + .list() + .get(0); + + Annotation statelessAnnotation = openRewriteJavaSource.getTypes().get(0).getAnnotations().get(0); + String annotationToTemplate = sut.mapToServiceAnnotation(statelessAnnotation); + + assertThat(annotationToTemplate).isEqualTo( + "@Service" + ); + } + + @Test + void shouldCreateTemplateStringForStatelessAnnotation_name() { + + JavaSource openRewriteJavaSource = TestProjectContext.buildProjectContext() + .withJavaSources( + "import javax.ejb.Stateless;\n" + + "@Stateless(mappedName=\"theMappedName\", name=\"beanName\")\n" + + "public class TheBean {}") + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2") + .build() + .getProjectJavaSources() + .list() + .get(0); + + Annotation statelessAnnotation = openRewriteJavaSource.getTypes().get(0).getAnnotations().get(0); + String annotationToTemplate = sut.mapToServiceAnnotation(statelessAnnotation); + + assertThat(annotationToTemplate).isEqualTo( + "@Service(\"beanName\")" + ); + } + + @Test + void shouldCreateTemplateStringForStatelessAnnotation_description() { + + JavaSource openRewriteJavaSource = TestProjectContext.buildProjectContext() + .withJavaSources( + "import javax.ejb.Stateless;\n" + + "@Stateless(description=\"a description\")\n" + + "public class TheBean {}") + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2") + .build() + .getProjectJavaSources() + .list() + .get(0); + + Annotation statelessAnnotation = openRewriteJavaSource.getTypes().get(0).getAnnotations().get(0); + String annotationToTemplate = sut.mapToServiceAnnotation(statelessAnnotation); + + assertThat(annotationToTemplate).isEqualTo( + "/**\n" + + "* a description\n" + + "*/\n" + + "@Service" + ); + } + + + @Test + void shouldCreateTemplateStringForStatelessAnnotation_allAttributes() { + + JavaSource openRewriteJavaSource = TestProjectContext.buildProjectContext() + .withJavaSources( + "import javax.ejb.Stateless;\n" + + "@Stateless(description=\"a description\", mappedName=\"theMappedName\", name=\"beanName\")\n" + + "public class TheBean {}") + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2") + .build() + .getProjectJavaSources() + .list() + .get(0); + + Annotation statelessAnnotation = openRewriteJavaSource.getTypes().get(0).getAnnotations().get(0); + String annotationToTemplate = sut.mapToServiceAnnotation(statelessAnnotation); + + assertThat(annotationToTemplate).isEqualTo( + "/**\n" + + "* a description\n" + + "*/\n" + + "@Service(\"beanName\")" + ); + } + + @Test + void shouldThrowIllegalArgumentExceptionForInvalidAnnotation() { + JavaSource openRewriteJavaSource = TestProjectContext.buildProjectContext() + .withJavaSources( + "@Deprecated\n" + + "public class TheBean {}") + .build() + .getProjectJavaSources() + .list() + .get(0); + + Annotation statelessAnnotation = openRewriteJavaSource.getTypes().get(0).getAnnotations().get(0); + + assertThrows(IllegalArgumentException.class, () -> sut.mapToServiceAnnotation(statelessAnnotation)); + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/recipes/EjbJarDeploymentDescriptorTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/recipes/EjbJarDeploymentDescriptorTest.java new file mode 100644 index 000000000..b0413acb2 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/recipes/EjbJarDeploymentDescriptorTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021 - 2023 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.ejb.recipes; + +import org.junit.jupiter.api.Test; +import org.springframework.sbm.common.migration.conditions.FileMatchingPatternExist; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.jee.ejb.actions.MigrateEjbDeploymentDescriptor; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.sbm.test.RecipeTestSupport.*; + +public class EjbJarDeploymentDescriptorTest { + + @Test + void ejbJarMigrationTest() { + Optional recipe = Optional.of(new MigrateEjbJarDeploymentDescriptorRecipe().ejbJarDeploymentDescriptor()); + + assertThatRecipeExists(recipe); + + assertThatRecipeHasActions(recipe, + MigrateEjbDeploymentDescriptor.class + ); + + MigrateEjbDeploymentDescriptor migrateEjbDeploymentDescriptor = getAction(recipe, MigrateEjbDeploymentDescriptor.class); + assertThatActionHasCondition(migrateEjbDeploymentDescriptor, FileMatchingPatternExist.class); + FileMatchingPatternExist fileMatchingPatternExist = getConditionFor(migrateEjbDeploymentDescriptor, FileMatchingPatternExist.class); + assertThat(fileMatchingPatternExist.getPattern()).isEqualTo("/**/ejb-jar.xml"); + } + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/recipes/MigrateStatelessEjbRecipeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/recipes/MigrateStatelessEjbRecipeTest.java new file mode 100644 index 000000000..a49b5bb85 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/recipes/MigrateStatelessEjbRecipeTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2021 - 2023 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.ejb.recipes; + +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.build.migration.actions.AddDependencies; +import org.springframework.sbm.build.migration.actions.RemoveDependenciesMatchingRegex; +import org.springframework.sbm.build.migration.conditions.NoDependencyExistMatchingRegex; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.java.migration.actions.AddTypeAnnotationToTypeAnnotatedWith; +import org.springframework.sbm.java.migration.actions.ReplaceTypeAction; +import org.springframework.sbm.java.migration.conditions.HasAnnotation; +import org.springframework.sbm.java.migration.conditions.HasImportStartingWith; +import org.springframework.sbm.java.migration.conditions.HasMemberAnnotation; +import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation; +import org.springframework.sbm.jee.ejb.actions.MigrateEjbAnnotations; +import org.springframework.sbm.jee.ejb.actions.MigrateLocalStatelessSessionBeans; +import org.springframework.sbm.jee.ejb.conditions.NoTransactionalAnnotationPresentOnTypeAnnotatedWith; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import static org.springframework.sbm.test.RecipeTestSupport.*; +import static org.assertj.core.api.Assertions.assertThat; + +public class MigrateStatelessEjbRecipeTest { + + @Test + void migrateStatelessEjbRecipe() { + testRecipe(Path.of("recipes/migrate-stateless-ejb.yaml"), recipes -> { + Optional recipe = recipes.getRecipeByName("migrate-stateless-ejb"); + + assertThatRecipeExists(recipe); + + assertThatRecipeHasCondition(recipe, HasImportStartingWith.class); + + assertThatRecipeHasActions(recipe, + MigrateEjbAnnotations.class, + ReplaceTypeAction.class, + ReplaceTypeAction.class, + MigrateLocalStatelessSessionBeans.class, + RemoveDependenciesMatchingRegex.class, + AddDependencies.class, + AddTypeAnnotationToTypeAnnotatedWith.class); + + MigrateEjbAnnotations migrateEjbAnnotations = getAction(recipe, MigrateEjbAnnotations.class); + HasAnnotation hasAnnotation = getConditionFor(migrateEjbAnnotations, HasAnnotation.class); + assertThat(hasAnnotation.getAnnotation()).isEqualTo("javax.ejb.EJB"); + + List replaceTypeActions = getActions(recipe, ReplaceTypeAction.class); + + assertThatActionHasCondition(replaceTypeActions.get(0), HasMemberAnnotation.class); + HasMemberAnnotation condition2 = getConditionFor(replaceTypeActions.get(0), HasMemberAnnotation.class); + assertThat(condition2.getAnnotation()).isEqualTo("javax.inject.Inject"); + assertThat(replaceTypeActions.get(0).getExistingType()).isEqualTo("javax.inject.Inject"); + assertThat(replaceTypeActions.get(0).getWithType()).isEqualTo("org.springframework.beans.factory.annotation.Autowired"); + + assertThatActionHasCondition(replaceTypeActions.get(1), HasTypeAnnotation.class); + HasTypeAnnotation condition4 = getConditionFor(replaceTypeActions.get(1), HasTypeAnnotation.class); + assertThat(condition4.getAnnotation()).isEqualTo("javax.ejb.Startup"); + assertThat(replaceTypeActions.get(1).getExistingType()).isEqualTo("javax.ejb.Startup"); + assertThat(replaceTypeActions.get(1).getWithType()).isEqualTo("org.springframework.stereotype.Service"); + + MigrateLocalStatelessSessionBeans migrateLocalStatelessSessionBeans = getAction(recipe, MigrateLocalStatelessSessionBeans.class); + assertThatActionHasCondition(migrateLocalStatelessSessionBeans, HasTypeAnnotation.class); + HasTypeAnnotation condition6 = getConditionFor(migrateLocalStatelessSessionBeans, HasTypeAnnotation.class); + assertThat(condition6.getAnnotation()).isEqualTo("javax.ejb.Stateless"); + + RemoveDependenciesMatchingRegex removeDependenciesAction = getAction(recipe, RemoveDependenciesMatchingRegex.class); + assertThat(removeDependenciesAction.getDependenciesRegex()).containsExactlyInAnyOrder( + "javax\\:javaee-api.*", + "javax\\.ejb\\:javax\\.ejb-api\\:.*", + "org\\.jboss\\.spec\\.javax\\.ejb\\:jboss-ejb-api_3.*"); + + AddDependencies addDependencies = getAction(recipe, AddDependencies.class); + assertThat(addDependencies.getDependencies()).containsExactly(Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-data-jpa").version("managed").build()); + NoDependencyExistMatchingRegex noDependencyExistMatchingRegex = getConditionFor(addDependencies, NoDependencyExistMatchingRegex.class); + assertThat(noDependencyExistMatchingRegex.getDependencies()).containsExactly("org.springframework.boot:spring-boot-starter-data-jpa"); + + AddTypeAnnotationToTypeAnnotatedWith addTransactionalAnnotationToTypeAnnotatedWith = getAction(recipe, AddTypeAnnotationToTypeAnnotatedWith.class); + assertThat(addTransactionalAnnotationToTypeAnnotatedWith.getAnnotatedWith()).isEqualTo("org.springframework.stereotype.Service"); + assertThat(addTransactionalAnnotationToTypeAnnotatedWith.getAnnotation()).isEqualTo("org.springframework.transaction.annotation.Transactional"); + assertThat(addTransactionalAnnotationToTypeAnnotatedWith.isAddAnnotationIfExists()).isFalse(); + assertThatActionHasCondition(addTransactionalAnnotationToTypeAnnotatedWith, NoTransactionalAnnotationPresentOnTypeAnnotatedWith.class); + NoTransactionalAnnotationPresentOnTypeAnnotatedWith hasServiceAnnotation = getConditionFor(addTransactionalAnnotationToTypeAnnotatedWith, NoTransactionalAnnotationPresentOnTypeAnnotatedWith.class); + assertThat(hasServiceAnnotation.getTypeAnnotatedWith()).isEqualTo("org.springframework.stereotype.Service"); + }); + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotationsTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotationsTest.java new file mode 100644 index 000000000..4019c7244 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotationsTest.java @@ -0,0 +1,344 @@ +/* + * Copyright 2021 - 2023 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.actions; + +import org.intellij.lang.annotations.Language; +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 ConvertJaxRsAnnotationsTest { + + @Test + void convertJaxRsMethodWithoutPathToSpringMvc() { + @Language("java") + String restControllerCode = """ + package com.example.jeerest.rest; + + import com.example.jeerest.Movie; + import com.example.jeerest.MoviesBean; + import org.springframework.beans.factory.annotation.Autowired; + + import javax.ws.rs.DELETE; + import javax.ws.rs.GET; + import javax.ws.rs.PUT; + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + import javax.ws.rs.Produces; + import javax.ws.rs.QueryParam; + import javax.ws.rs.core.MediaType; + import java.util.List; + + @Path("movies") + @Produces({"application/json"}) + public class MoviesRest { + @GET + public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, + @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { + return service.getMovies(first, max, field, searchTerm); + } + } + """; + + @Language("java") + String expected = """ + package com.example.jeerest.rest; + + import com.example.jeerest.Movie; + import com.example.jeerest.MoviesBean; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; + + import javax.ws.rs.DELETE; + import javax.ws.rs.PUT; + import javax.ws.rs.PathParam; + import javax.ws.rs.QueryParam; + import javax.ws.rs.core.MediaType; + import java.util.List; + + + @RestController + @RequestMapping(value = "movies", produces = {"application/json"}) + public class MoviesRest { + @RequestMapping(method = RequestMethod.GET) + public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, + @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { + return service.getMovies(first, max, field, searchTerm); + } + } + """; + + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(restControllerCode) + .withBuildFileHavingDependencies("org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", "org.springframework.boot:spring-boot-starter-web:2.4.2") + .build(); + ConvertJaxRsAnnotations sut = new ConvertJaxRsAnnotations(); + sut.apply(projectContext); + + assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); + } + + @Test + void replaceMethodAnnotationsWithOneAnnotation() { + + String sourceCode = """ + import javax.ws.rs.Path; + import javax.ws.rs.Consumes; + import javax.ws.rs.POST; + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + import javax.ws.rs.Produces; + import javax.ws.rs.core.MediaType; + + + @Path("/hello") + class ControllerClass { + @POST + @Path("/json/{name}") + @Produces({"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML}) + @Consumes("application/json") + public String getHelloWorldJSON( @PathParam("name") String name) { + return "Hello"; + } + }"""; + + String expected = """ + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; + import javax.ws.rs.PathParam; + import javax.ws.rs.core.MediaType; + + + @RestController + @RequestMapping(value = "/hello") + class ControllerClass { + @RequestMapping(value = "/json/{name}", produces = {"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML}, consumes = "application/json", method = RequestMethod.POST) + public String getHelloWorldJSON( @PathParam("name") String name) { + return "Hello"; + } + }"""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies("org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", "org.springframework.boot:spring-boot-starter-web:2.4.2") + .build(); + + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + + ConvertJaxRsAnnotations sut = new ConvertJaxRsAnnotations(); + sut.apply(projectContext); + + assertThat(javaSource.print()).isEqualTo(expected); + } + + + @Test + void replaceMethodAnnotations() { + String sourceCode = """ + import javax.ws.rs.Path; + import javax.ws.rs.Consumes; + import javax.ws.rs.*; + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + import javax.ws.rs.Produces; + import javax.ws.rs.core.MediaType; + + + @Path("/hello") + class ControllerClass { + @POST + @GET + @PUT + @DELETE + @Path("/json/{name}") + @Produces({"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML}) + @Consumes("application/json") + public String getHelloWorldJSON(@PathParam("name") String name) { + return "Hello"; + } + public String notAnEndpoint(@PathParam("name") String name) { + return "Hello"; + } + }"""; + + @Language("java") + String expected = """ + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; + + import javax.ws.rs.PathParam; + import javax.ws.rs.core.MediaType; + + + @RestController + @RequestMapping(value = "/hello") + class ControllerClass { + @RequestMapping(value = "/json/{name}", produces = {"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML}, consumes = "application/json", method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.PUT, RequestMethod.DELETE}) + public String getHelloWorldJSON(@PathParam("name") String name) { + return "Hello"; + } + public String notAnEndpoint(@PathParam("name") String name) { + return "Hello"; + } + }"""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies("org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", "org.springframework:spring-web:5.3.8") + .build(); + + ConvertJaxRsAnnotations sut = new ConvertJaxRsAnnotations(); + + sut.apply(projectContext); + + assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); + } + + @Test + void migrateToController() { + String sourceCode = """ + package com.example.jee.app; + + import javax.inject.Inject; + import javax.ws.rs.*; + import javax.ws.rs.core.MediaType; + import java.util.stream.Collectors; + + @Path("/") + public class PersonController { + + @POST + @Path("/json/{name}") + @Produces("application/json") + @Consumes("application/json") + public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { + return ""; + } + + @GET + @Path("/json") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public String getAllPersons() throws Exception { + return ""; + } + + @POST + @Path("/xml/{name}") + @Produces(MediaType.APPLICATION_XML) + @Consumes(MediaType.APPLICATION_XML) + public String getHelloWorldXML(@PathParam("name") String name) throws Exception { + return ""; + } + }"""; + + String expected = """ + package com.example.jee.app; + + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; + + import javax.ws.rs.PathParam; + import javax.ws.rs.core.MediaType; + + @RestController + @RequestMapping(value = "/") + public class PersonController { + + @RequestMapping(value = "/json/{name}", produces = "application/json", consumes = "application/json", method = RequestMethod.POST) + public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { + return ""; + } + + @RequestMapping(value = "/json", produces = MediaType.APPLICATION_JSON, consumes = MediaType.APPLICATION_JSON, method = RequestMethod.GET) + public String getAllPersons() throws Exception { + return ""; + } + + @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) + public String getHelloWorldXML(@PathParam("name") String name) throws Exception { + return ""; + } + }"""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies("org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", "org.springframework:spring-web:5.3.8") + .build(); + + ConvertJaxRsAnnotations sut = new ConvertJaxRsAnnotations(); + + sut.apply(projectContext); + + assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); + } + + @Test + void addRequestBodyOverParameter() { + String sourceCode = """ + import javax.ws.rs.Path; + import javax.ws.rs.POST; + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + + @Path("/hello") + class ControllerClass { + @POST + @Path("/json/{name}") + public String create(@PathParam("name") String name, String data) { + return "Hello"; + } + }"""; + + String expected = """ + import org.springframework.web.bind.annotation.RequestBody; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; + import javax.ws.rs.PathParam; + + @RestController + @RequestMapping(value = "/hello") + class ControllerClass { + @RequestMapping(value = "/json/{name}", method = RequestMethod.POST) + public String create(@PathParam("name") String name, @RequestBody String data) { + return "Hello"; + } + }"""; + + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-web:4.1.7.RELEASE", + "org.springframework:spring-webmvc:4.1.7.RELEASE") + .build(); + + ConvertJaxRsAnnotations sut = new ConvertJaxRsAnnotations(); + sut.apply(projectContext); + assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java new file mode 100644 index 000000000..63160697c --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021 - 2023 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.build.migration.actions.AddDependencies; +import org.springframework.sbm.engine.recipe.OpenRewriteDeclarativeRecipeAdapter; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.java.JavaRecipeAction; +import org.springframework.sbm.java.migration.actions.ReplaceTypeAction; +import org.springframework.sbm.jee.jaxrs.MigrateJaxRsRecipe; +import org.springframework.sbm.jee.jaxrs.actions.ConvertJaxRsAnnotations; +import org.springframework.sbm.test.RecipeTestSupport; + +import java.util.Optional; + +public class BootifyJaxRsAnnotationsRecipeTest { + + @Test + void test() { + + Recipe jaxRsRecipe = new MigrateJaxRsRecipe().jaxRs(null); + Optional recipe = Optional.of(jaxRsRecipe); + RecipeTestSupport.assertThatRecipeExists(recipe); + RecipeTestSupport.assertThatRecipeHasActions(recipe, + AddDependencies.class, + ConvertJaxRsAnnotations.class, + ReplaceTypeAction.class, + ReplaceTypeAction.class, + ReplaceTypeAction.class, + JavaRecipeAction.class, + JavaRecipeAction.class, + JavaRecipeAction.class, + JavaRecipeAction.class, + JavaRecipeAction.class, + JavaRecipeAction.class, + OpenRewriteDeclarativeRecipeAdapter.class); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CacheControlTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CacheControlTest.java new file mode 100644 index 000000000..591218083 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CacheControlTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 - 2023 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.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CacheControlTest { + + final private AbstractAction action = + new AbstractAction() { + @Override + public void apply(ProjectContext context) { + SwapCacheControl r = new SwapCacheControl(); + context.getProjectJavaSources().apply(r); + } + }; + + @Test + void general() { + + String javaSource = "" + + "import javax.ws.rs.core.CacheControl;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public CacheControl test() {\n" + + " CacheControl c = new CacheControl();\n" + + " c.setSMaxAge(3600);\n" + + " return c;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.CacheControl;\n" + + "\n" + + "import java.util.concurrent.TimeUnit;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public CacheControl test() {\n" + + " CacheControl c = CacheControl.empty();\n" + + " c.sMaxAge(3600, TimeUnit.SECONDS);\n" + + " return c;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax:javaee-api:8.0") + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ConvertJaxRsAnnotationsTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ConvertJaxRsAnnotationsTest.java new file mode 100644 index 000000000..fbf2d133b --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ConvertJaxRsAnnotationsTest.java @@ -0,0 +1,239 @@ +/* + * Copyright 2021 - 2023 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.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation; +import org.springframework.sbm.jee.jaxrs.actions.ConvertJaxRsAnnotations; +import org.springframework.sbm.project.resource.TestProjectContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Fabian Krüger + */ +public class ConvertJaxRsAnnotationsTest { + + private final static String SPRING_VERSION = "5.3.13"; + + @Test + void noPathOnMethodLevel() { + @Language("java") + String restControllerCode = """ + package com.example.jeerest.rest; + + import com.example.jeerest.Movie; + import com.example.jeerest.MoviesBean; + import org.springframework.beans.factory.annotation.Autowired; + + import javax.ws.rs.DELETE; + import javax.ws.rs.GET; + import javax.ws.rs.PUT; + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + import javax.ws.rs.Produces; + import javax.ws.rs.QueryParam; + import javax.ws.rs.core.MediaType; + import java.util.List; + + @Path("movies") + @Produces({"application/json"}) + public class MoviesRest { + + @GET + @Path("{id}") + public Movie find(@PathParam("id") Long id) { + return service.find(id); + } + + @GET + public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, + @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { + return service.getMovies(first, max, field, searchTerm); + } + } + """; + + ProjectContext context = TestProjectContext.buildProjectContext() + .withJavaSources(restControllerCode) + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION, + "org.springframework:spring-web:"+SPRING_VERSION + ) + .build(); + + ConvertJaxRsAnnotations convertJaxRsAnnotations = ConvertJaxRsAnnotations + .builder() + .condition(HasTypeAnnotation.builder().annotation("javax.ws.rs.Path").build()) + .description("Convert JAX-RS annotations into Spring Boot annotations.") + .build(); + + convertJaxRsAnnotations.apply(context); + + + @Language("java") + String expected = + """ + package com.example.jeerest.rest; + + import com.example.jeerest.Movie; + import com.example.jeerest.MoviesBean; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; + + import javax.ws.rs.DELETE; + import javax.ws.rs.PUT; + import javax.ws.rs.PathParam; + import javax.ws.rs.QueryParam; + import javax.ws.rs.core.MediaType; + import java.util.List; + + + @RestController + @RequestMapping(value = "movies", produces = {"application/json"}) + public class MoviesRest { + + @RequestMapping(value = "{id}", method = RequestMethod.GET) + public Movie find(@PathParam("id") Long id) { + return service.find(id); + } + + @RequestMapping(method = RequestMethod.GET) + public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, + @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { + return service.getMovies(first, max, field, searchTerm); + } + } + """; + + assertThat(context.getProjectJavaSources().list().get(0).print()).isEqualTo( + expected + ); + } + + @Test + void classAnnotatedWithProducesAndConsumes() { + @Language("java") + String restControllerCode = """ + package com.example.jeerest.rest; + + import javax.ws.rs.Consumes; + import javax.ws.rs.Path; + import javax.ws.rs.Produces; + + @Path("movies") + @Consumes("application/x-www-form-urlencoded") + @Produces("application/json") + public class MoviesRest { + } + """; + + ProjectContext context = TestProjectContext.buildProjectContext() + .withJavaSources(restControllerCode) + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION, + "org.springframework:spring-web:"+SPRING_VERSION + ) + .build(); + + ConvertJaxRsAnnotations convertJaxRsAnnotations = ConvertJaxRsAnnotations + .builder() + .condition(HasTypeAnnotation.builder().annotation("javax.ws.rs.Path").build()) + .description("Convert JAX-RS annotations into Spring Boot annotations.") + .build(); + + convertJaxRsAnnotations.apply(context); + + + @Language("java") + String expected = + """ + package com.example.jeerest.rest; + + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RestController; + + + @RestController + @RequestMapping(value = "movies", consumes = "application/x-www-form-urlencoded", produces = "application/json") + public class MoviesRest { + } + """; + + assertThat(context.getProjectJavaSources().list().get(0).print()).isEqualTo( + expected + ); + } + + @Test + void classAnnotatedWithConsumes() { + @Language("java") + String restControllerCode = """ + package com.example.jeerest.rest; + + import javax.ws.rs.Consumes; + import javax.ws.rs.Path; + + @Path("movies") + @Consumes("application/x-www-form-urlencoded") + public class MoviesRest { + } + """; + + ProjectContext context = TestProjectContext.buildProjectContext() + .withJavaSources(restControllerCode) + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION, + "org.springframework:spring-web:"+SPRING_VERSION + ) + .build(); + + ConvertJaxRsAnnotations convertJaxRsAnnotations = ConvertJaxRsAnnotations + .builder() + .condition(HasTypeAnnotation.builder().annotation("javax.ws.rs.Path").build()) + .description("Convert JAX-RS annotations into Spring Boot annotations.") + .build(); + + convertJaxRsAnnotations.apply(context); + + + @Language("java") + String expected = + """ + package com.example.jeerest.rest; + + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RestController; + + + @RestController + @RequestMapping(value = "movies", consumes = "application/x-www-form-urlencoded") + public class MoviesRest { + } + """; + + assertThat(context.getProjectJavaSources().list().get(0).print()).isEqualTo( + expected + ); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java new file mode 100644 index 000000000..87fe932ff --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java @@ -0,0 +1,410 @@ +/* + * Copyright 2021 - 2023 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/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java new file mode 100644 index 000000000..955fa105b --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 2021 - 2023 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 diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java new file mode 100644 index 000000000..ec5d3bea9 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java @@ -0,0 +1,571 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.java.JavaParser; +import org.springframework.rewrite.parser.RewriteExecutionContext; +import org.springframework.rewrite.parser.SpringRewriteProperties; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.impl.RewriteJavaParser; +import org.springframework.sbm.project.resource.SbmApplicationProperties; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; +import org.junit.jupiter.api.Test; + +import java.util.function.Supplier; + +import static org.assertj.core.api.Assertions.assertThat; + +class ReplaceMediaTypeTest { + + private final static String SPRING_VERSION = "5.3.13"; + + private final Supplier javaParserSupplier = () -> new RewriteJavaParser(new SpringRewriteProperties(), + new RewriteExecutionContext()); + + final private AbstractAction action = new AbstractAction() { + @Override + public void apply(ProjectContext context) { + ReplaceMediaType r = new ReplaceMediaType(javaParserSupplier); + context.getProjectJavaSources().apply(r); + } + }; + + @Test + void replaceMediaTypeConstant_with_removed_import() { + String sourceCode = + """ + import javax.ws.rs.core.MediaType; + class ControllerClass { + public String getHelloWorldJSON(String name) { + return MediaType.APPLICATION_XML; + } + + }"""; + + String expected = """ + import org.springframework.http.MediaType; + + class ControllerClass { + public String getHelloWorldJSON(String name) { + return MediaType.APPLICATION_XML_VALUE; + } + + }"""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .build(); + + ReplaceMediaType sut = new ReplaceMediaType(javaParserSupplier); + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + javaSource.apply(sut); + + assertThat(javaSource.print()).isEqualTo(expected); + } + + @Test + void constants() { + + String javaSource = """ + import javax.ws.rs.core.MediaType; + + public class TestController { + + public void respond() { + MediaType m1 = MediaType.APPLICATION_ATOM_XML_TYPE; + String s1 = MediaType.APPLICATION_ATOM_XML; + + MediaType m2 = MediaType.APPLICATION_FORM_URLENCODED_TYPE; + String s2 = MediaType.APPLICATION_FORM_URLENCODED; + + MediaType m3 = MediaType.APPLICATION_JSON_TYPE; + String s3 = MediaType.APPLICATION_JSON; + + MediaType m4 = MediaType.APPLICATION_JSON_PATCH_JSON_TYPE; + String s4 = MediaType.APPLICATION_JSON_PATCH_JSON; + + MediaType m5 = MediaType.APPLICATION_OCTET_STREAM_TYPE; + String s5 = MediaType.APPLICATION_OCTET_STREAM; + + MediaType m7 = MediaType.APPLICATION_SVG_XML_TYPE; + String s7 = MediaType.APPLICATION_SVG_XML; + + MediaType m8 = MediaType.APPLICATION_XHTML_XML_TYPE; + String s8 = MediaType.APPLICATION_XHTML_XML; + + MediaType m9 = MediaType.APPLICATION_XML_TYPE; + String s9 = MediaType.APPLICATION_XML; + + MediaType m10 = MediaType.MULTIPART_FORM_DATA_TYPE; + String s10 = MediaType.MULTIPART_FORM_DATA; + + MediaType m11 = MediaType.SERVER_SENT_EVENTS_TYPE; + String s11 = MediaType.SERVER_SENT_EVENTS; + + MediaType m12 = MediaType.TEXT_HTML_TYPE; + String s12 = MediaType.TEXT_HTML; + + MediaType m13 = MediaType.TEXT_PLAIN_TYPE; + String s13 = MediaType.TEXT_PLAIN; + + MediaType m14 = MediaType.TEXT_XML_TYPE; + String s14 = MediaType.TEXT_XML; + + MediaType m15 = MediaType.WILDCARD_TYPE; + String s15 = MediaType.WILDCARD; + + String s16 = MediaType.CHARSET_PARAMETER; + + String s17 = MediaType.MEDIA_TYPE_WILDCARD; + } + } + """; + + String expected = """ + import org.springframework.http.MediaType; + import org.springframework.util.MimeType; + + public class TestController { + + public void respond() { + MediaType m1 = MediaType.APPLICATION_ATOM_XML; + String s1 = MediaType.APPLICATION_ATOM_XML_VALUE; + + MediaType m2 = MediaType.APPLICATION_FORM_URLENCODED; + String s2 = MediaType.APPLICATION_FORM_URLENCODED_VALUE; + + MediaType m3 = MediaType.APPLICATION_JSON; + String s3 = MediaType.APPLICATION_JSON_VALUE; + + MediaType m4 = MediaType.APPLICATION_JSON_PATCH_JSON; + String s4 = MediaType.APPLICATION_JSON_PATCH_JSON_VALUE; + + MediaType m5 = MediaType.APPLICATION_OCTET_STREAM; + String s5 = MediaType.APPLICATION_OCTET_STREAM_VALUE; + + MediaType m7 = MediaType.APPLICATION_SVG_XML; + String s7 = MediaType.APPLICATION_SVG_XML_VALUE; + + MediaType m8 = MediaType.APPLICATION_XHTML_XML; + String s8 = MediaType.APPLICATION_XHTML_XML_VALUE; + + MediaType m9 = MediaType.APPLICATION_XML; + String s9 = MediaType.APPLICATION_XML_VALUE; + + MediaType m10 = MediaType.MULTIPART_FORM_DATA; + String s10 = MediaType.MULTIPART_FORM_DATA_VALUE; + + MediaType m11 = MediaType.TEXT_EVENT_STREAM; + String s11 = MediaType.TEXT_EVENT_STREAM_VALUE; + + MediaType m12 = MediaType.TEXT_HTML; + String s12 = MediaType.TEXT_HTML_VALUE; + + MediaType m13 = MediaType.TEXT_PLAIN; + String s13 = MediaType.TEXT_PLAIN_VALUE; + + MediaType m14 = MediaType.TEXT_XML; + String s14 = MediaType.TEXT_XML_VALUE; + + MediaType m15 = MediaType.ALL; + String s15 = MediaType.ALL_VALUE; + + String s16 = MimeType.PARAM_CHARSET; + + String s17 = MimeType.WILDCARD_TYPE; + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void instanceMethodIsCompatible() { + + String javaSource = """ + import javax.ws.rs.core.MediaType; + + public class TestController { + + public boolean respond() { + MediaType m1 = MediaType.APPLICATION_ATOM_XML_TYPE; + return MediaType.APPLICATION_XML_TYPE.isCompatible(m1); + } + } + """; + + String expected = """ + import org.springframework.http.MediaType; + + public class TestController { + + public boolean respond() { + MediaType m1 = MediaType.APPLICATION_ATOM_XML; + return MediaType.APPLICATION_XML.isCompatibleWith(m1); + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-web:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void instanceMethodWithCharset() { + + String javaSource = """ + import javax.ws.rs.core.MediaType; + + public class TestController { + + public MediaType respond() { + MediaType m1 = MediaType.APPLICATION_ATOM_XML_TYPE; + return m1.withCharset("UTF-8"); + } + } + """; + + String expected = """ + import org.springframework.http.MediaType; + + import java.nio.charset.Charset; + + public class TestController { + + public MediaType respond() { + MediaType m1 = MediaType.APPLICATION_ATOM_XML; + return new MediaType(m1, Charset.forName("UTF-8")); + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void instanceMethodWithCharset2() { + + String javaSource = """ + import javax.ws.rs.core.MediaType; + + public class TestController { + + public MediaType respond() { + return MediaType.APPLICATION_ATOM_XML_TYPE.withCharset("UTF-8"); + } + } + """; + + String expected = """ + import org.springframework.http.MediaType; + + import java.nio.charset.Charset; + + public class TestController { + + public MediaType respond() { + return new MediaType(MediaType.APPLICATION_ATOM_XML, Charset.forName("UTF-8")); + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void constructor1() { + + String javaSource = """ + import javax.ws.rs.core.MediaType; + + public class TestController { + + public MediaType respond() { + return new MediaType("foo", "bar"); + } + } + """; + + String expected = """ + import org.springframework.http.MediaType; + + public class TestController { + + public MediaType respond() { + return new MediaType("foo", "bar"); + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void constructor2() { + + String javaSource = """ + import javax.ws.rs.core.MediaType; + + public class TestController { + + public MediaType respond() { + String type = "foo"; + return new MediaType(type, "bar", "UTF-8"); + } + } + """; + + String expected = """ + import org.springframework.http.MediaType; + + import java.nio.charset.Charset; + + public class TestController { + + public MediaType respond() { + String type = "foo"; + return new MediaType(type, "bar", Charset.forName("UTF-8")); + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void constructor3() { + + String javaSource = """ + import javax.ws.rs.core.MediaType; + + public class TestController { + + public MediaType respond() { + return new MediaType(); + } + } + """; + + String expected = """ + import org.springframework.http.MediaType; + import org.springframework.util.MimeType; + + public class TestController { + + public MediaType respond() { + return new MediaType(MimeType.WILDCARD_TYPE, MimeType.WILDCARD_TYPE); + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void constructor4() { + + String javaSource = """ + import javax.ws.rs.core.MediaType; + import java.util.Map; + + public class TestController { + + public MediaType respond() { + return new MediaType("blah", "UTF-8", Map.of("foo", "bar")); + } + } + """; + + String expected = """ + import org.springframework.http.MediaType; + + import java.util.Map; + + public class TestController { + + public MediaType respond() { + return new MediaType("blah", "UTF-8", Map.of("foo", "bar")); + } + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void replaceMediaTypeConstant() { + + String sourceCode = + """ + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + import javax.ws.rs.core.MediaType; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + + @Path("/hello") + class ControllerClass { + @Path("/json/{name}") + @RequestMapping(value = "/json/{name}", produces = {"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML}, consumes = "application/json", method = RequestMethod.POST)" + public String getHelloWorldJSON(@PathParam("name") String name) { + return "Hello"; + } + }"""; + + String expected = + """ + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + + import org.springframework.http.MediaType; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + + @Path("/hello") + class ControllerClass { + @Path("/json/{name}") + @RequestMapping(value = "/json/{name}", produces = {"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML_VALUE}, consumes = "application/json", method = RequestMethod.POST)" + public String getHelloWorldJSON(@PathParam("name") String name) { + return "Hello"; + } + }"""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(sourceCode) + .withBuildFileHavingDependencies( + "org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .build(); + + ReplaceMediaType r = new ReplaceMediaType(javaParserSupplier); + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + javaSource.apply(r); + + assertThat(javaSource.print()).isEqualTo(expected); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java new file mode 100644 index 000000000..713197a31 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java @@ -0,0 +1,390 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.java.tree.J; +import org.springframework.rewrite.parser.RewriteExecutionContext; +import org.springframework.rewrite.parser.SpringRewriteProperties; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.impl.RewriteJavaParser; +import org.springframework.sbm.project.resource.SbmApplicationProperties; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ResponseBuilderTest { + + private final static String SPRING_VERSION = "5.3.13"; + + final private AbstractAction action = + new AbstractAction() { + @Override + public void apply(ProjectContext context) { + ReplaceResponseEntityBuilder r = new ReplaceResponseEntityBuilder(); + context.getProjectJavaSources().apply(r); + } + }; + + @Test + void allow() { + + String javaSource = "" + + "import java.util.Set;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder test() {\n" + + " ResponseBuilder b;\n" + + " b.allow(\"POST\", \"PUT\");\n" + + " b.allow(Set.of(\"GET\"));\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import java.util.Set;\n" + + "\n" + + "import org.springframework.http.HttpMethod;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity.BodyBuilder test() {\n" + + " ResponseEntity.BodyBuilder b;\n" + + " b.allow(HttpMethod.resolve(\"POST\"), HttpMethod.resolve(\"PUT\"));\n" + + " b.allow(Set.of(\"GET\").stream().map(HttpMethod::resolve).toArray(String[]::new));\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void expires() { + + String javaSource = "" + + "import java.util.Date;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder test() {\n" + + " ResponseBuilder b;\n" + + " b.expires(new Date(100000));\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import java.util.Date;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity.BodyBuilder test() {\n" + + " ResponseEntity.BodyBuilder b;\n" + + " b.headers(h -> h.setExpires(new Date(100000).toInstant()));\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax:javaee-api:8.0") + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void language() { + + String javaSource = "" + + "import java.util.Locale;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder test() {\n" + + " ResponseBuilder b;\n" + + " b.language(\"ua\");\n" + + " b.language(Locale.ITALY);\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import java.util.Locale;\n" + + "\n" + + "import org.springframework.http.HttpHeaders;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity.BodyBuilder test() {\n" + + " ResponseEntity.BodyBuilder b;\n" + + " b.headers(h -> h.set(HttpHeaders.CONTENT_LANGUAGE, \"ua\"));\n" + + " b.headers(h -> h.setContentLanguage(Locale.ITALY));\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-web:5.3.18") + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void lastModified() { + + String javaSource = "" + + "import java.util.Date;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder test() {\n" + + " ResponseBuilder b;\n" + + " b.lastModified(new Date(100000));\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import java.util.Date;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity.BodyBuilder test() {\n" + + " ResponseEntity.BodyBuilder b;\n" + + " b.lastModified(new Date(100000).toInstant());\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax:javaee-api:8.0") + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void replaceAll() { + + String javaSource = "" + + "import javax.ws.rs.core.MultivaluedMap;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder test() {\n" + + " ResponseBuilder b;\n" + + " MultivaluedMap m;\n" + + " b.replaceAll(m);\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import javax.ws.rs.core.MultivaluedMap;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity.BodyBuilder test() {\n" + + " ResponseEntity.BodyBuilder b;\n" + + " MultivaluedMap m;\n" + + " b.headers(h -> {\n" + + " h.clear();\n" + + " h.addAll(m);\n" + + " });\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void tag() { + + String javaSource = "" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder test() {\n" + + " ResponseBuilder b;\n" + + " b.tag(\"foo\");\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity.BodyBuilder test() {\n" + + " ResponseEntity.BodyBuilder b;\n" + + " b.eTag(\"foo\");\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void type() { + + String javaSource = "" + + "import javax.ws.rs.core.MediaType;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder test() {\n" + + " ResponseBuilder b;\n" + + " b.type(MediaType.APPLICATION_JSON_TYPE);\n" + + " b.type(\"application/json\");\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import javax.ws.rs.core.MediaType;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity.BodyBuilder test() {\n" + + " ResponseEntity.BodyBuilder b;\n" + + " b.contentType(MediaType.APPLICATION_JSON_TYPE);\n" + + " b.headers(h -> h.set(HttpHeaders.CONTENT_TYPE, \"application/json\"));\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax:javaee-api:8.0") + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + + // verify it compiles + List parse = new RewriteJavaParser(new SpringRewriteProperties(), + new RewriteExecutionContext()) + .parse(actual) + .map(J.CompilationUnit.class::cast) + .toList(); + + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java new file mode 100644 index 000000000..c76c3b9dc --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -0,0 +1,955 @@ +/* + * Copyright 2021 - 2023 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.openrewrite.java.JavaParser; +import org.springframework.rewrite.parser.RewriteExecutionContext; +import org.springframework.rewrite.parser.SpringRewriteProperties; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.impl.RewriteJavaParser; +import org.springframework.sbm.project.resource.SbmApplicationProperties; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; +import org.junit.jupiter.api.Test; +import org.openrewrite.Recipe; + +import java.util.function.Supplier; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ResponseEntityReplacementTest { + + private final static String SPRING_VERSION = "5.3.13"; + + final private AbstractAction action = + new AbstractAction() { + @Override + public void apply(ProjectContext context) { + Supplier javaParserSupplier = () -> new RewriteJavaParser(new SpringRewriteProperties(), + new RewriteExecutionContext()); + Recipe r = new SwapResponseWithResponseEntity(javaParserSupplier).doNext(new ReplaceMediaType(javaParserSupplier)); + context.getProjectJavaSources().apply(r); + } + }; + + + @Test + void testUnsupportedStaticCall() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.status(200, \"All good\").build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return /* SBM FIXME: Couldn't find exact replacement for status(int, java.lang.String) - dropped java.lang.String argument */ ResponseEntity.status(200).build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + + + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testUnsupportedBuilderCall() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.status(200).tag(\"My Tag\").build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.status(200).eTag(\"My Tag\").build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testUnsupportedBuilder() { + + String javaSource = "" + + "import java.util.stream.LongStream;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public LongStream respond() {\n" + + " return LongStream.builder().add(1).add(2).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = javaSource; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testOnlyReturnStatementBuilder() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " Response r = Response.status(200).build();\n" + + " return r;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " ResponseEntity r = ResponseEntity.status(200).build();\n" + + " return r;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + + @Test + void testSimplestCase() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.status(200).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.status(200).build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testReplaceBuildWithBody() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.ok(\"All good!\").build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.ok().body(\"All good!\");\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testReplaceOkWithBody() { + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " Response r = Response.ok(\"great!\").build();\n" + + " return r;\n" + + " }\n" + + "}\n" + + ""; + + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " ResponseEntity r = ResponseEntity.ok().body(\"great!\");\n" + + " return r;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testReplaceOkWithMediaTypeAndBody() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import javax.ws.rs.core.MediaType;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.ok(\"All good!\", MediaType.APPLICATION_JSON_TYPE).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.MediaType;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(\"All good!\");\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + + @Test + void testReplaceOkWithMediaTypeStringAndBody() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.ok(\"All good!\", \"application/json\").build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.MediaType;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.ok().contentType(MediaType.parseMediaType(\"application/json\")).body(\"All good!\");\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void accepted_1() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.accepted().build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.accepted().build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + + @Test + void accepted_2() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.accepted(\"Correct\").build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.accepted().body(\"Correct\");\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void created() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import java.net.URI;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " URI uri = URI.create(\"https://spring.io\");\n" + + " return Response.created(uri).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import java.net.URI;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " URI uri = URI.create(\"https://spring.io\");\n" + + " return ResponseEntity.created(uri).build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void fromResponse() { + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " Response r = Response.ok(\"great!\").build();\n" + + " return Response.fromResponse(r).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " ResponseEntity r = ResponseEntity.ok().body(\"great!\");\n" + + " return ResponseEntity.status(r.getStatusCode()).headers(r.getHeaders()).body(r.getBody());\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void notModified() { + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " Response.notModified(\"great!\");\n" + + " return Response.notModified().build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " ResponseEntity.status(HttpStatus.NOT_MODIFIED).eTag(\"great!\");\n" + + " return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void seeOther() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import java.net.URI;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " URI uri = URI.create(\"https://spring.io\");\n" + + " return Response.seeOther(uri).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import java.net.URI;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " URI uri = URI.create(\"https://spring.io\");\n" + + " return ResponseEntity.status(HttpStatus.SEE_OTHER).location(uri).build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void serverError() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.serverError().build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.status(HttpStatus.SERVER_ERROR).build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void temporaryRedirect() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import java.net.URI;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " URI uri = URI.create(\"https://spring.io\");\n" + + " return Response.temporaryRedirect(uri).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import java.net.URI;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " URI uri = URI.create(\"https://spring.io\");\n" + + " return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(uri).build();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void instanceMethods() { + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import javax.ws.rs.core.GenericType;\n" + + "import java.lang.annotation.Annotation;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public String respond() {\n" + + " Response r = Response.ok().build();\n" + + " r.getAllowedMethods();\n" + + " r.bufferEntity();\n" + + " r.close();\n" + + " r.getCookies();\n" + + " r.getDate();\n" + + " r.getEntity();\n" + + " r.bufferEntity();\n" + + " r.getEntityTag();\n" + + " r.getHeaders();\n" + + " r.getHeaderString(\"Accept\");\n" + + " r.getLanguage();\n" + + " r.getLastModified();\n" + + " r.getLength();\n" + + " r.getLink(\"Something\");\n" + + " r.getLinkBuilder(\"Something\");\n" + + " r.getLinks();\n" + + " r.getLocation();\n" + + " r.getMediaType();\n" + + " r.getMetadata();\n" + + " r.getStatus();\n" + + " r.getStatusInfo();\n" + + " r.getStringHeaders();\n" + + " r.hasEntity();\n" + + " r.hasLink(\"Something\");\n" + + " r.readEntity(String.class, new Annotation[0]);\n" + + " r.readEntity(GenericType.forInstance(\"Something\"));\n" + + " r.readEntity(GenericType.forInstance(\"Something\"), new Annotation[0]);\n" + + " return r.readEntity(String.class);\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "import java.util.Date;\n" + + "import java.util.stream.Collectors;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public String respond() {\n" + + " ResponseEntity r = ResponseEntity.ok().build();\n" + + " r.getHeaders().getAllow().stream().map(m -> m.toString()).collect(Collectors.toList());\n" + + " r.bufferEntity();\n" + + " r.close();\n" + + " r.getCookies();\n" + + " new Date(r.getHeaders().getDate());\n" + + " r.getBody();\n" + + " r.bufferEntity();\n" + + " r.getHeaders().getETag();\n" + + " r.getHeaders();\n" + + " r.getHeaders().get(\"Accept\").stream().collect(Collectors.joining(\", \"));\n" + + " r.getHeaders().getContentLanguage();\n" + + " new Date(r.getHeaders().getLastModified());\n" + + " r.getHeaders().getContentLength();\n" + + " r.getLink(\"Something\");\n" + + " r.getLinkBuilder(\"Something\");\n" + + " r.getLinks();\n" + + " r.getHeaders().getLocation();\n" + + " r.getHeaders().getContentType();\n" + + " r.getHeaders();\n" + + " r.getStatusCodeValue();\n" + + " r.getStatusCode();\n" + + " r.getHeaders();\n" + + " r.hasBody();\n" + + " r.hasLink(\"Something\");\n" + + " r.getBody();\n" + + " r.getBody();\n" + + " r.getBody();\n" + + " return r.getBody();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void chain_1() { + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import javax.ws.rs.core.MediaType;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.status(200).entity(\"Hello\").tag(\"My Tag\").type(MediaType.TEXT_PLAIN_TYPE).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.MediaType;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.status(200).eTag(\"My Tag\").contentType(MediaType.TEXT_PLAIN).body(\"Hello\");\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void chain_2() { + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "import javax.ws.rs.core.MediaType;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder respond() {\n" + + " return Response.status(200).entity(\"Hello\").tag(\"My Tag\").type(MediaType.TEXT_PLAIN_TYPE);\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.MediaType;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.status(200).eTag(\"My Tag\").contentType(MediaType.TEXT_PLAIN).body(\"Hello\");\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusFamilyTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusFamilyTest.java new file mode 100644 index 000000000..891a29720 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusFamilyTest.java @@ -0,0 +1,175 @@ +/* + * Copyright 2021 - 2023 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.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ResponseStatusFamilyTest { + + private final static String SPRING_VERSION = "5.3.13"; + + final private AbstractAction action = + new AbstractAction() { + @Override + public void apply(ProjectContext context) { + SwapFamilyForSeries r = new SwapFamilyForSeries(); + context.getProjectJavaSources().apply(r); + } + }; + + @Test + void enumConstantsTest() { + + String javaSource = "" + + "import javax.ws.rs.core.Response.Status.Family;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " Family f1 = Family.INFORMATIONAL;\n" + + " Family f2 = Family.SUCCESSFUL;\n" + + " Family f3 = Family.REDIRECTION;\n" + + " Family f4 = Family.CLIENT_ERROR;\n" + + " Family f5 = Family.SERVER_ERROR;\n" + + " \n" + + " int code = 201;\n" + + " Family custom = Family.familyOf(code);\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "import org.springframework.http.HttpStatus.Series;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " HttpStatus.Series f1 = Series.INFORMATIONAL;\n" + + " HttpStatus.Series f2 = Series.SUCCESSFUL;\n" + + " HttpStatus.Series f3 = Series.REDIRECTION;\n" + + " HttpStatus.Series f4 = Series.CLIENT_ERROR;\n" + + " HttpStatus.Series f5 = Series.SERVER_ERROR;\n" + + " \n" + + " int code = 201;\n" + + " HttpStatus.Series custom = HttpStatus.Series.resolve(code);\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void otherEnumConstantsTest() { + + String javaSource = "" + + "import javax.ws.rs.core.Response.Status.Family;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " Family f = Family.OTHER;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "import org.springframework.http.HttpStatus.Series;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " HttpStatus.Series f = HttpStatus.Series.OTHER;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax:javaee-api:8.0") + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void staticImportMethod() { + + String javaSource = "" + + "import static javax.ws.rs.core.Response.Status.Family.familyOf;\n" + + "import javax.ws.rs.core.Response.Status.Family;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " int code = 201;\n" + + " Family custom = familyOf(code);\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " int code = 201;\n" + + " HttpStatus.Series custom = HttpStatus.Series.resolve(code);\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax:javaee-api:8.0") + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java new file mode 100644 index 000000000..2e8b7761b --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java @@ -0,0 +1,422 @@ +/* + * Copyright 2021 - 2023 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.springframework.rewrite.parser.RewriteExecutionContext; +import org.springframework.rewrite.parser.SpringRewriteProperties; +import org.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.impl.RewriteJavaParser; +import org.springframework.sbm.project.resource.SbmApplicationProperties; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ResponseStatusTest { + + private final static String SPRING_VERSION = "5.3.5"; + + final private AbstractAction action = + new AbstractAction() { + @Override + public void apply(ProjectContext context) { + SwapStatusForHttpStatus r = new SwapStatusForHttpStatus(() -> new RewriteJavaParser(new SpringRewriteProperties(), + new RewriteExecutionContext())); + context.getProjectJavaSources().apply(r); + } + }; + + + @Test + void testHttpStatusOK() { + + String javaSource = "" + + "import javax.ws.rs.core.Response.Status;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Status respond() {\n" + + " return Status.OK;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public HttpStatus respond() {\n" + + " return HttpStatus.OK;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testOkWithTopLevelType() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response.Status respond() {\n" + + " return Response.Status.OK;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public HttpStatus respond() {\n" + + " return HttpStatus.OK;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-web:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testResponseStatusAsParameter() { + + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void respond() {\n" + + " System.out.println(Response.Status.NOT_FOUND);\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void respond() {\n" + + " System.out.println(HttpStatus.NOT_FOUND);\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testHttpStatusEntityTooLarge() { + + String javaSource = "" + + "import javax.ws.rs.core.Response.Status;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Status respond() {\n" + + " Status s = Status.REQUEST_ENTITY_TOO_LARGE;\n" + + " return s;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public HttpStatus respond() {\n" + + " HttpStatus s = HttpStatus.PAYLOAD_TOO_LARGE;\n" + + " return s;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-web:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testHttpStatusCode() { + + String javaSource = "" + + "import javax.ws.rs.core.Response.Status;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public int respond() {\n" + + " Status s = Status.REQUEST_ENTITY_TOO_LARGE;\n" + + " return s.getStatusCode();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public int respond() {\n" + + " HttpStatus s = HttpStatus.PAYLOAD_TOO_LARGE;\n" + + " return s.getValue();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testHttpStatusToEnum() { + + String javaSource = "" + + "import javax.ws.rs.core.Response.Status;\n" + + "import javax.ws.rs.core.Response.StatusType;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Status respond() {\n" + + " StatusType s = Status.OK;\n" + + " return s.toEnum();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public HttpStatus respond() {\n" + + " HttpStatus s = HttpStatus.OK;\n" + + " return s;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void testStatusGetFamily() { + + String javaSource = "" + + "import javax.ws.rs.core.Response.Status;\n" + + "import javax.ws.rs.core.Response.Status.Family;\n" + + "import javax.ws.rs.core.Response.StatusType;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Family respond() {\n" + + " StatusType s = Status.OK;\n" + + " return s.getFamily();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public HttpStatus.Series respond() {\n" + + " HttpStatus s = HttpStatus.OK;\n" + + " return s.series();\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void mini_integration_test_1() { + String javaSource = "package com.hotjoe.services.exception;\n" + + "\n" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class NotFoundServiceException extends ServiceException {\n" + + " private static final long serialVersionUID = 1736707486401545199L;\n" + + "\n" + + " public NotFoundServiceException() {\n" + + " super();\n" + + " }\n" + + "\n" + + " public NotFoundServiceException(String message) {\n" + + " super(message);\n" + + " }\n" + + "\n" + + " public NotFoundServiceException(Throwable cause) {\n" + + " super(cause);\n" + + " }\n" + + "\n" + + "\n" + + " public NotFoundServiceException(String message, Throwable cause) {\n" + + " super(message, cause);\n" + + " }\n" + + "\n" + + " @Override\n" + + " public Response.Status getStatus() {\n" + + " return Response.Status.NOT_FOUND;\n" + + " }\n" + + "}"; + + String expected = "package com.hotjoe.services.exception;\n" + + "\n" + + "import org.springframework.http.HttpStatus;\n" + + "\n" + + "public class NotFoundServiceException extends ServiceException {\n" + + " private static final long serialVersionUID = 1736707486401545199L;\n" + + "\n" + + " public NotFoundServiceException() {\n" + + " super();\n" + + " }\n" + + "\n" + + " public NotFoundServiceException(String message) {\n" + + " super(message);\n" + + " }\n" + + "\n" + + " public NotFoundServiceException(Throwable cause) {\n" + + " super(cause);\n" + + " }\n" + + "\n" + + "\n" + + " public NotFoundServiceException(String message, Throwable cause) {\n" + + " super(message, cause);\n" + + " }\n" + + "\n" + + " @Override\n" + + " public HttpStatus getStatus() {\n" + + " return HttpStatus.NOT_FOUND;\n" + + " }\n" + + "}"; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + + } + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttpHeadersTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttpHeadersTest.java new file mode 100644 index 000000000..7e65d9bbc --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttpHeadersTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2021 - 2023 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.springframework.sbm.engine.recipe.AbstractAction; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SwapHttpHeadersTest { + + private final static String SPRING_VERSION = "5.3.13"; + + final private AbstractAction action = + new AbstractAction() { + @Override + public void apply(ProjectContext context) { + SwapHttHeaders r = new SwapHttHeaders(); + context.getProjectJavaSources().apply(r); + } + }; + + @Test + void constants() { + + String javaSource = "" + + "import javax.ws.rs.core.HttpHeaders;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " String s1 = HttpHeaders.COOKIE;\n" + + " String s2 = HttpHeaders.CONTENT_ID;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpHeaders;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " String s1 = HttpHeaders.COOKIE;\n" + + " String s2 = HttpHeaders.CONTENT_ID;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-core:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } + + @Test + void instanceMethods() { + + String javaSource = "" + + "import javax.ws.rs.core.HttpHeaders;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " HttpHeaders h;\n" + + " h.getAcceptableLanguages();\n" + + " h.getDate();\n" + + " h.getHeaderString(\"Accept\");\n" + + " h.getLanguage();\n" + + " h.getLength();\n" + + " h.getMediaType();\n" + + " h.getRequestHeader(\"Accept\");\n" + + " var all = h.getRequestHeaders();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpHeaders;\n" + + "\n" + + "import java.util.Date;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public void test() {\n" + + " HttpHeaders h;\n" + + " h.getAcceptLanguageAsLocales();\n" + + " new Date(h.getDate());\n" + + " String.join(\", \", h.get(\"Accept\"));\n" + + " h.getContentLanguage();\n" + + " h.getContentLength();\n" + + " h.getContentType();\n" + + " h.get(\"Accept\");\n" + + " var all = h;\n" + + " }\n" + + "}\n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies( + "javax:javaee-api:8.0", + "org.springframework:spring-web:"+SPRING_VERSION + ) + .withJavaSources(javaSource) + .build(); + + action.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(0).print(); + assertThat(actual) + .as(TestDiff.of(actual, expected)) + .isEqualTo(expected); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/GenerateWebServicesTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/GenerateWebServicesTest.java new file mode 100644 index 000000000..7c392b823 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/GenerateWebServicesTest.java @@ -0,0 +1,853 @@ +/* + * Copyright 2021 - 2023 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.jaxws; + +import org.springframework.rewrite.resource.RewriteSourceFileHolder; +import org.springframework.sbm.engine.recipe.UserInteractions; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; +import freemarker.template.Configuration; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +/** + * @author Alex Boyko + */ +@SpringBootTest +@Disabled("https://github.com/pivotal/spring-boot-migrator/issues/244") +public class GenerateWebServicesTest { + + @SpringBootApplication + public static class TestApp { + } + + @MockBean + UserInteractions ui; + + @Autowired + private Configuration configuration; + + private GenerateWebServices action; + + @BeforeEach + void setUp() { + if (action == null) { + action = new GenerateWebServices(ui, configuration); + } + } + + + @Test + void sanity() throws Exception { + + String javaClass = "package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import javax.jws.WebService;\n" + + "\n" + + "@Stateless\n" + + "@WebService(\n" + + " portName = \"CalculatorPort\",\n" + + " serviceName = \"CalculatorService\",\n" + + " targetNamespace = \"http://superbiz.org/wsdl\",\n" + + " endpointInterface = \"org.superbiz.calculator.ws.CalculatorWs\")\n" + + "public class Calculator implements CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2) {\n" + + " return add1 + add2;\n" + + " }\n" + + "\n" + + " public int multiply(int mul1, int mul2) {\n" + + " return mul1 * mul2;\n" + + " }\n" + + "}\n"; + + String endpointInterface = "package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.jws.WebService;\n" + + "\n" + + "@WebService(targetNamespace = \"http://superbiz.org/wsdl\")\n" + + "public interface CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2);\n" + + "\n" + + " public int multiply(int mul1, int mul2);\n" + + "}"; + + String pom = "\n" + + " 4.0.0\n" + + " org.superbiz\n" + + " simple-webservice\n" + + " jar\n" + + " 8.0.8-SNAPSHOT\n" + + " TomEE :: Examples :: Simple Webservice\n" + + " \n" + + " UTF-8\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " org.apache.maven.plugins\n" + + " maven-compiler-plugin\n" + + " 3.5.1\n" + + " \n" + + " 1.8\n" + + " 1.8\n" + + " \n" + + " \n" + + " \n" + + " org.tomitribe.transformer\n" + + " org.eclipse.transformer.maven\n" + + " 0.1.1a\n" + + " \n" + + " jakartaee9\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " run\n" + + " \n" + + " package\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withMavenRootBuildFileSource(pom) + .withBuildFileHavingDependencies("org.apache.tomee.bom:tomee-plus-api:8.0.7") + .withJavaSources(javaClass, endpointInterface) + .build(); + + String wsdlPathStr = "./testcode/wsdl/calculator.wsdl"; + String question = String.format(GenerateWebServices.QUESTION, "Calculator"); + when(ui.askForInput(question)).thenReturn(wsdlPathStr); + + action.apply(projectContext); + + assertThat(projectContext.getBuildFile().print()).isEqualTo("\n" + + " 4.0.0\n" + + " org.superbiz\n" + + " simple-webservice\n" + + " jar\n" + + " 8.0.8-SNAPSHOT\n" + + " TomEE :: Examples :: Simple Webservice\n" + + " \n" + + " UTF-8\n" + + " src/generated\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " org.apache.maven.plugins\n" + + " maven-compiler-plugin\n" + + " 3.5.1\n" + + " \n" + + " 1.8\n" + + " 1.8\n" + + " \n" + + " \n" + + " \n" + + " org.tomitribe.transformer\n" + + " org.eclipse.transformer.maven\n" + + " 0.1.1a\n" + + " \n" + + " jakartaee9\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " run\n" + + " \n" + + " package\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " org.jvnet.jaxb2.maven2\n" + + " maven-jaxb2-plugin\n" + + " 0.14.0\n" + + " \n" + + " \n" + + " \n" + + " generate\n" + + " \n" + + " \n" + + " ${project.basedir}/src/main/resources\n" + + " *.wsdl\n" + + " ${generated-sources.dir}\n" + + " org.superbiz.wsdl\n" + + " \n" + + " \n" + + " \n" + + " org.codehaus.mojo\n" + + " build-helper-maven-plugin\n" + + " \n" + + " \n" + + " \n" + + " add-source\n" + + " \n" + + " generate-sources\n" + + " \n" + + " ${generated-sources.dir}\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""); + + assertThat(projectContext.getProjectJavaSources().list().size()).isEqualTo(4); + + assertThat(projectContext.getProjectJavaSources().list().get(0).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class Calculator implements CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2) {\n" + + " return add1 + add2;\n" + + " }\n" + + "\n" + + " public int multiply(int mul1, int mul2) {\n" + + " return mul1 * mul2;\n" + + " }\n" + + "}\n"); + + assertThat(projectContext.getProjectJavaSources().list().get(1).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "public interface CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2);\n" + + "\n" + + " public int multiply(int mul1, int mul2);\n" + + "}"); + + assertThat(projectContext.getProjectJavaSources().list().get(2).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import org.springframework.boot.web.servlet.ServletRegistrationBean;\n" + + "import org.springframework.context.ApplicationContext;\n" + + "import org.springframework.context.annotation.Bean;\n" + + "import org.springframework.context.annotation.Configuration;\n" + + "import org.springframework.core.io.ClassPathResource;\n" + + "import org.springframework.ws.config.annotation.EnableWs;\n" + + "import org.springframework.ws.config.annotation.WsConfigurerAdapter;\n" + + "import org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition;\n" + + "import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition;\n" + + "import org.springframework.ws.transport.http.MessageDispatcherServlet;\n" + + "\n" + + "@EnableWs\n" + + "@Configuration\n" + + "public class WebServiceConfig extends WsConfigurerAdapter {\n" + + "\n" + + " @Bean\n" + + " public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {\n" + + " MessageDispatcherServlet servlet = new MessageDispatcherServlet();\n" + + " servlet.setApplicationContext(applicationContext);\n" + + " servlet.setTransformWsdlLocations(true);\n" + + " return new ServletRegistrationBean<>(servlet, \"/simple-webservice/*\");\n" + + " }\n" + + "\n" + + " @Bean(name = \"Calculator\")\n" + + " SimpleWsdl11Definition calculator() {\n" + + " SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();\n" + + " wsdl11Definition.setWsdl(new ClassPathResource(\"calculator.wsdl\"));\n" + + " return wsdl11Definition;\n" + + " }\n" + + "\n" + + "}\n"); + + assertThat(projectContext.getProjectJavaSources().list().get(3).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import org.springframework.ws.server.endpoint.annotation.Endpoint;\n" + + "import org.springframework.ws.server.endpoint.annotation.PayloadRoot;\n" + + "import org.springframework.ws.server.endpoint.annotation.RequestPayload;\n" + + "import org.springframework.ws.server.endpoint.annotation.ResponsePayload;\n" + + "import org.superbiz.calculator.ws.CalculatorWs;\n" + + "import org.superbiz.wsdl.Multiply;\n" + + "import org.superbiz.wsdl.MultiplyResponse;\n" + + "import org.superbiz.wsdl.Sum;\n" + + "import org.superbiz.wsdl.SumResponse;\n" + + "\n" + + "@Endpoint\n" + + "public class CalculatorWsEndpoint {\n" + + "\n" + + " private static final String NAMESPACE_URI = \"http://superbiz.org/wsdl\";\n" + + "\n" + + " private CalculatorWs calculatorWs;\n" + + "\n" + + " CalculatorWsEndpoint(CalculatorWs calculatorWs) {\n" + + " this.calculatorWs = calculatorWs;\n" + + " }\n" + + "\n" + + " @PayloadRoot(namespace = NAMESPACE_URI, localPart = \"sum\")\n" + + " @ResponsePayload\n" + + " public SumResponse sum(@RequestPayload Sum request) {\n" + + " SumResponse response = new SumResponse();\n" + + " response.setReturn(calculatorWs.sum(request.getArg0(), request.getArg1()));\n" + + " return response;\n" + + " }\n" + + "\n" + + " @PayloadRoot(namespace = NAMESPACE_URI, localPart = \"multiply\")\n" + + " @ResponsePayload\n" + + " public MultiplyResponse multiply(@RequestPayload Multiply request) {\n" + + " MultiplyResponse response = new MultiplyResponse();\n" + + " response.setReturn(calculatorWs.multiply(request.getArg0(), request.getArg1()));\n" + + " return response;\n" + + " }\n" + + "\n" + + "}\n"); + + Optional> wsdl = projectContext.getProjectResources().stream() + .filter(r -> r.getAbsolutePath().endsWith(Path.of("src/main/resources/calculator.wsdl"))) + .findFirst(); + + assertThat(wsdl).isPresent(); + + assertThat(wsdl.get().print().trim()).isEqualTo(IOUtils.toString(Files.newInputStream(Path.of(wsdlPathStr)), Charset.defaultCharset().name()).trim()); + + verify(ui).askForInput(question); + verifyNoMoreInteractions(ui); + + } + + @Test + void noMavenChangesToGeneratePojo() throws Exception { + + String javaClass = "package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import javax.jws.WebService;\n" + + "\n" + + "@Stateless\n" + + "@WebService(\n" + + " portName = \"CalculatorPort\",\n" + + " serviceName = \"CalculatorService\",\n" + + " targetNamespace = \"http://superbiz.org/wsdl\",\n" + + " endpointInterface = \"org.superbiz.calculator.ws.CalculatorWs\")\n" + + "public class Calculator implements CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2) {\n" + + " return add1 + add2;\n" + + " }\n" + + "\n" + + " public int multiply(int mul1, int mul2) {\n" + + " return mul1 * mul2;\n" + + " }\n" + + "}\n"; + + + String endpointInterface = "package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.jws.WebService;\n" + + "\n" + + "@WebService(targetNamespace = \"http://superbiz.org/wsdl\")\n" + + "public interface CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2);\n" + + "\n" + + " public int multiply(int mul1, int mul2);\n" + + "}"; + + String pojo = "" + + "package org.superbiz.wsdl;\n" + + "\n" + + "import javax.xml.bind.annotation.XmlAccessType;\n" + + "import javax.xml.bind.annotation.XmlAccessorType;\n" + + "import javax.xml.bind.annotation.XmlType;\n" + + "\n" + + "@XmlAccessorType(XmlAccessType.FIELD)\n" + + "@XmlType(name = \"sum\", propOrder = {\n" + + " \"arg0\",\n" + + " \"arg1\"\n" + + "})\n" + + "public class Sum {\n" + + "\n" + + " protected int arg0;\n" + + " protected int arg1;\n" + + "\n" + + " /**\n" + + " * Gets the value of the arg0 property.\n" + + " * \n" + + " */\n" + + " public int getArg0() {\n" + + " return arg0;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Sets the value of the arg0 property.\n" + + " * \n" + + " */\n" + + " public void setArg0(int value) {\n" + + " this.arg0 = value;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Gets the value of the arg1 property.\n" + + " * \n" + + " */\n" + + " public int getArg1() {\n" + + " return arg1;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Sets the value of the arg1 property.\n" + + " * \n" + + " */\n" + + " public void setArg1(int value) {\n" + + " this.arg1 = value;\n" + + " }\n" + + "\n" + + "}\n"; + + String pom = "\n" + + " 4.0.0\n" + + " org.superbiz\n" + + " simple-webservice\n" + + " jar\n" + + " 8.0.8-SNAPSHOT\n" + + " TomEE :: Examples :: Simple Webservice\n" + + " \n" + + " UTF-8\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withMavenRootBuildFileSource(pom) + .withBuildFileHavingDependencies("org.apache.tomee.bom:tomee-plus-api:8.0.7") + .withJavaSources(javaClass, endpointInterface, pojo) + .build(); + + String wsdlPathStr = "./testcode/wsdl/calculator.wsdl"; + String question = String.format(GenerateWebServices.QUESTION, "Calculator"); + when(ui.askForInput(question)).thenReturn(wsdlPathStr); + + action.apply(projectContext); + + // No changes to pom - as POJO code is supposed to be present + assertThat(projectContext.getBuildFile().print()).isEqualTo(pom); + + verify(ui).askForInput(question); + verifyNoMoreInteractions(ui); + + } + + @Test + void oneWayAnnotation() throws Exception { + + String javaClass = "package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import javax.jws.WebService;\n" + + "\n" + + "@Stateless\n" + + "@WebService(\n" + + " portName = \"CalculatorPort\",\n" + + " serviceName = \"CalculatorService\",\n" + + " targetNamespace = \"http://superbiz.org/wsdl\",\n" + + " endpointInterface = \"org.superbiz.calculator.ws.CalculatorWs\")\n" + + "public class Calculator implements CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2) {\n" + + " return add1 + add2;\n" + + " }\n" + + "}\n"; + + String endpointInterface = "package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.jws.Oneway;\n" + + "import javax.jws.WebService;\n" + + "\n" + + "@WebService(targetNamespace = \"http://superbiz.org/wsdl\")\n" + + "public interface CalculatorWs {\n" + + "\n" + + " @Oneway\n" + + " public int sum(int add1, int add2);\n" + + "}"; + + String pom = "\n" + + " 4.0.0\n" + + " org.superbiz\n" + + " simple-webservice\n" + + " jar\n" + + " 8.0.8-SNAPSHOT\n" + + " TomEE :: Examples :: Simple Webservice\n" + + " \n" + + " UTF-8\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " org.apache.maven.plugins\n" + + " maven-compiler-plugin\n" + + " 3.5.1\n" + + " \n" + + " 1.8\n" + + " 1.8\n" + + " \n" + + " \n" + + " \n" + + " org.tomitribe.transformer\n" + + " org.eclipse.transformer.maven\n" + + " 0.1.1a\n" + + " \n" + + " jakartaee9\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " run\n" + + " \n" + + " package\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withMavenRootBuildFileSource(pom) + .withBuildFileHavingDependencies("org.apache.tomee.bom:tomee-plus-api:8.0.7") + .withJavaSources(javaClass, endpointInterface) + .build(); + + String wsdlPathStr = "./testcode/wsdl/calculator.wsdl"; + String question = String.format(GenerateWebServices.QUESTION, "Calculator"); + when(ui.askForInput(question)).thenReturn(wsdlPathStr); + + action.apply(projectContext); + + assertThat(projectContext.getProjectJavaSources().list().size()).isEqualTo(4); + + assertThat(projectContext.getProjectJavaSources().list().get(0).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class Calculator implements CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2) {\n" + + " return add1 + add2;\n" + + " }\n" + + "}\n"); + + assertThat(projectContext.getProjectJavaSources().list().get(1).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "public interface CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2);\n" + + "}"); + + assertThat(projectContext.getProjectJavaSources().list().get(2).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import org.springframework.boot.web.servlet.ServletRegistrationBean;\n" + + "import org.springframework.context.ApplicationContext;\n" + + "import org.springframework.context.annotation.Bean;\n" + + "import org.springframework.context.annotation.Configuration;\n" + + "import org.springframework.core.io.ClassPathResource;\n" + + "import org.springframework.ws.config.annotation.EnableWs;\n" + + "import org.springframework.ws.config.annotation.WsConfigurerAdapter;\n" + + "import org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition;\n" + + "import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition;\n" + + "import org.springframework.ws.transport.http.MessageDispatcherServlet;\n" + + "\n" + + "@EnableWs\n" + + "@Configuration\n" + + "public class WebServiceConfig extends WsConfigurerAdapter {\n" + + "\n" + + " @Bean\n" + + " public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {\n" + + " MessageDispatcherServlet servlet = new MessageDispatcherServlet();\n" + + " servlet.setApplicationContext(applicationContext);\n" + + " servlet.setTransformWsdlLocations(true);\n" + + " return new ServletRegistrationBean<>(servlet, \"/simple-webservice/*\");\n" + + " }\n" + + "\n" + + " @Bean(name = \"Calculator\")\n" + + " SimpleWsdl11Definition calculator() {\n" + + " SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();\n" + + " wsdl11Definition.setWsdl(new ClassPathResource(\"calculator.wsdl\"));\n" + + " return wsdl11Definition;\n" + + " }\n" + + "\n" + + "}\n"); + + assertThat(projectContext.getProjectJavaSources().list().get(3).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import org.springframework.ws.server.endpoint.annotation.Endpoint;\n" + + "import org.springframework.ws.server.endpoint.annotation.PayloadRoot;\n" + + "import org.springframework.ws.server.endpoint.annotation.RequestPayload;\n" + + "import org.superbiz.calculator.ws.CalculatorWs;\n" + + "import org.superbiz.wsdl.Sum;\n" + + "\n" + + "@Endpoint\n" + + "public class CalculatorWsEndpoint {\n" + + "\n" + + " private static final String NAMESPACE_URI = \"http://superbiz.org/wsdl\";\n" + + "\n" + + " private CalculatorWs calculatorWs;\n" + + "\n" + + " CalculatorWsEndpoint(CalculatorWs calculatorWs) {\n" + + " this.calculatorWs = calculatorWs;\n" + + " }\n" + + "\n" + + " @PayloadRoot(namespace = NAMESPACE_URI, localPart = \"sum\")\n" + + " public void sum(@RequestPayload Sum request) {\n" + + " calculatorWs.sum(request.getArg0(), request.getArg1());\n" + + " }\n" + + "\n" + + "}\n"); + + Optional> wsdl = projectContext.getProjectResources().stream() + .filter(r -> r.getAbsolutePath().endsWith(Path.of("src/main/resources/calculator.wsdl"))) + .findFirst(); + + assertThat(wsdl).isPresent(); + + InputStream inputStream = Files.newInputStream(Path.of(wsdlPathStr)); + assertThat(wsdl.get().print().trim()).isEqualTo(IOUtils.toString(inputStream, Charset.defaultCharset().name()).trim()); + + verify(ui).askForInput(question); + verifyNoMoreInteractions(ui); + + } + + @Test + void webAnnotations() { + + String javaClass = "package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "import javax.jws.WebService;\n" + + "\n" + + "@Stateless\n" + + "@WebService(\n" + + " portName = \"CalculatorPort\",\n" + + " serviceName = \"CalculatorService\",\n" + + " targetNamespace = \"http://superbiz.org/wsdl\",\n" + + " endpointInterface = \"org.superbiz.calculator.ws.CalculatorWs\")\n" + + "public class Calculator implements CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2) {\n" + + " return add1 + add2;\n" + + " }\n" + + "\n" + + " public int multiply(int mul1, int mul2) {\n" + + " return mul1 * mul2;\n" + + " }\n" + + "}\n"; + + String endpointInterface = "package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.jws.WebMethod;\n" + + "import javax.jws.WebParam;\n" + + "import javax.jws.WebResult;\n" + + "import javax.jws.WebService;\n" + + "\n" + + "@WebService(targetNamespace = \"http://superbiz.org/wsdl\")\n" + + "public interface CalculatorWs {\n" + + "\n" + + " @WebMethod(operationName = \"add\")\n" + + " @WebResult(name = \"result\")\n" + + " public int sum(@WebParam(name = \"firstParameter\") int add1, int add2);\n" + + "\n" + + " public int multiply(int mul1, @WebParam(name = \"times\") int mul2);\n" + + "}"; + + String pom = "\n" + + " 4.0.0\n" + + " org.superbiz\n" + + " simple-webservice\n" + + " jar\n" + + " 8.0.8-SNAPSHOT\n" + + " TomEE :: Examples :: Simple Webservice\n" + + " \n" + + " UTF-8\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " org.apache.maven.plugins\n" + + " maven-compiler-plugin\n" + + " 3.5.1\n" + + " \n" + + " 1.8\n" + + " 1.8\n" + + " \n" + + " \n" + + " \n" + + " org.tomitribe.transformer\n" + + " org.eclipse.transformer.maven\n" + + " 0.1.1a\n" + + " \n" + + " jakartaee9\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " run\n" + + " \n" + + " package\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withMavenRootBuildFileSource(pom) + .withBuildFileHavingDependencies("org.apache.tomee.bom:tomee-plus-api:8.0.7") + .withJavaSources(javaClass, endpointInterface) + .build(); + + String wsdlPathStr = "./testcode/wsdl/calculator.wsdl"; + String question = String.format(GenerateWebServices.QUESTION, "Calculator"); + when(ui.askForInput(question)).thenReturn(wsdlPathStr); + + action.apply(projectContext); + + assertThat(projectContext.getProjectJavaSources().list().size()).isEqualTo(4); + + assertThat(projectContext.getProjectJavaSources().list().get(0).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import javax.ejb.Stateless;\n" + + "\n" + + "@Stateless\n" + + "public class Calculator implements CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2) {\n" + + " return add1 + add2;\n" + + " }\n" + + "\n" + + " public int multiply(int mul1, int mul2) {\n" + + " return mul1 * mul2;\n" + + " }\n" + + "}\n"); + + assertThat(projectContext.getProjectJavaSources().list().get(1).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "public interface CalculatorWs {\n" + + "\n" + + " public int sum(int add1, int add2);\n" + + "\n" + + " public int multiply(int mul1, int mul2);\n" + + "}"); + + assertThat(projectContext.getProjectJavaSources().list().get(2).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import org.springframework.boot.web.servlet.ServletRegistrationBean;\n" + + "import org.springframework.context.ApplicationContext;\n" + + "import org.springframework.context.annotation.Bean;\n" + + "import org.springframework.context.annotation.Configuration;\n" + + "import org.springframework.core.io.ClassPathResource;\n" + + "import org.springframework.ws.config.annotation.EnableWs;\n" + + "import org.springframework.ws.config.annotation.WsConfigurerAdapter;\n" + + "import org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition;\n" + + "import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition;\n" + + "import org.springframework.ws.transport.http.MessageDispatcherServlet;\n" + + "\n" + + "@EnableWs\n" + + "@Configuration\n" + + "public class WebServiceConfig extends WsConfigurerAdapter {\n" + + "\n" + + " @Bean\n" + + " public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {\n" + + " MessageDispatcherServlet servlet = new MessageDispatcherServlet();\n" + + " servlet.setApplicationContext(applicationContext);\n" + + " servlet.setTransformWsdlLocations(true);\n" + + " return new ServletRegistrationBean<>(servlet, \"/simple-webservice/*\");\n" + + " }\n" + + "\n" + + " @Bean(name = \"Calculator\")\n" + + " SimpleWsdl11Definition calculator() {\n" + + " SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();\n" + + " wsdl11Definition.setWsdl(new ClassPathResource(\"calculator.wsdl\"));\n" + + " return wsdl11Definition;\n" + + " }\n" + + "\n" + + "}\n"); + + assertThat(projectContext.getProjectJavaSources().list().get(3).getResource().print()).isEqualTo("package org.superbiz.calculator.ws;\n" + + "\n" + + "import org.springframework.ws.server.endpoint.annotation.Endpoint;\n" + + "import org.springframework.ws.server.endpoint.annotation.PayloadRoot;\n" + + "import org.springframework.ws.server.endpoint.annotation.RequestPayload;\n" + + "import org.springframework.ws.server.endpoint.annotation.ResponsePayload;\n" + + "import org.superbiz.calculator.ws.CalculatorWs;\n" + + "import org.superbiz.wsdl.Add;\n" + + "import org.superbiz.wsdl.AddResponse;\n" + + "import org.superbiz.wsdl.Multiply;\n" + + "import org.superbiz.wsdl.MultiplyResponse;\n" + + "\n" + + "@Endpoint\n" + + "public class CalculatorWsEndpoint {\n" + + "\n" + + " private static final String NAMESPACE_URI = \"http://superbiz.org/wsdl\";\n" + + "\n" + + " private CalculatorWs calculatorWs;\n" + + "\n" + + " CalculatorWsEndpoint(CalculatorWs calculatorWs) {\n" + + " this.calculatorWs = calculatorWs;\n" + + " }\n" + + "\n" + + " @PayloadRoot(namespace = NAMESPACE_URI, localPart = \"add\")\n" + + " @ResponsePayload\n" + + " public AddResponse add(@RequestPayload Add request) {\n" + + " AddResponse response = new AddResponse();\n" + + " response.setResult(calculatorWs.sum(request.getFirstParameter(), request.getArg1()));\n" + + " return response;\n" + + " }\n" + + "\n" + + " @PayloadRoot(namespace = NAMESPACE_URI, localPart = \"multiply\")\n" + + " @ResponsePayload\n" + + " public MultiplyResponse multiply(@RequestPayload Multiply request) {\n" + + " MultiplyResponse response = new MultiplyResponse();\n" + + " response.setReturn(calculatorWs.multiply(request.getArg0(), request.getTimes()));\n" + + " return response;\n" + + " }\n" + + "\n" + + "}\n"); + + verify(ui).askForInput(question); + verifyNoMoreInteractions(ui); + + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptorTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptorTest.java new file mode 100644 index 000000000..8cac71b9a --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptorTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2021 - 2023 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.jaxws; + +import org.springframework.rewrite.parser.RewriteExecutionContext; +import org.springframework.sbm.GitHubIssue; +import org.springframework.sbm.java.api.Type; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.xml.tree.Xml; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Alex Boyko + */ +public class WebServiceDescriptorTest { + + @Test + void wsdlParsing() throws Exception { + Xml.Document doc = GenerateWebServices.parseWsdl(Path.of("../../testcode/simple-webservice/calculator.wsdl"), new RewriteExecutionContext()); + WebServiceDescriptor d = new WebServiceDescriptor(null, null, doc, null); + assertThat(d.getNsUri()).isEqualTo("http://superbiz.org/wsdl"); + assertThat(d.getPackageName()).isEqualTo("org.superbiz.wsdl"); + assertThat(d.getPathContext()).isEqualTo("simple-webservice"); + assertThat(d.getWsdlDefBeanName()).isEqualTo("Calculator"); + } + + @GitHubIssue("https://github.com/pivotal/spring-boot-migrator/issues/286") + @Test + void wsdlParsing2(@TempDir Path tmpDir) throws IOException { + String typeAnnotatedAsWebService = + "import javax.jws.WebMethod;\n" + + "import javax.jws.WebParam;\n" + + "import javax.jws.WebResult;\n" + + "import javax.jws.WebService;\n" + + "@WebService(targetNamespace = \"http://foo.com/jee/jaxws\")\n" + + "public interface CalculatorWs {\n" + + "\n" + + " @WebResult(name = \"additionResult\")\n" + + " @WebMethod(operationName = \"addition\")\n" + + " Integer sum(@WebParam(name = \"firstNumber\") int add1, @WebParam(name = \"secondNumber\") int add2);\n" + + "\n" + + " Integer multiply(int mul1, int mul2);\n" + + "}"; + + String wsdl = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + Path wsdlPath = tmpDir.resolve("calculator.wsdl"); + Files.write(wsdlPath, wsdl.getBytes()); + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withJavaSources(typeAnnotatedAsWebService) + .withBuildFileHavingDependencies("javax:javaee-api:8.0.1") + .build(); + + Type openRewriteType = projectContext.getProjectJavaSources().list().get(0).getTypes().get(0); + + Xml.Document doc = GenerateWebServices.parseWsdl(wsdlPath, new RewriteExecutionContext()); + WebServiceDescriptor d = new WebServiceDescriptor(openRewriteType, null, doc, null); + assertThat(d.getNsUri()).isEqualTo("http://foo.com/jee/jaxws"); + assertThat(d.getPackageName()).isEqualTo("com.foo.jee.jaxws"); + assertThat(d.getPathContext()).isEqualTo("Calculator"); // error + assertThat(d.getWsdlDefBeanName()).isEqualTo("CalculatorService"); + } + + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/WebServiceOperationTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/WebServiceOperationTest.java new file mode 100644 index 000000000..e0f1d3744 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxws/WebServiceOperationTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 - 2023 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.jaxws; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Alex Boyko + */ +public class WebServiceOperationTest { + + @Test + void simple() { + WebServiceOperation.Type input = new WebServiceOperation.Type(); + input.setSimpleName("Sum"); + input.setPackageName("org.superbiz.wsdl"); + input.setFields(new String[]{"p", "arg1"}); + + WebServiceOperation.Type output = new WebServiceOperation.Type(); + output.setSimpleName("SumResponse"); + output.setPackageName("org.superbiz.wsdl"); + output.setFields(new String[]{"return"}); + + WebServiceOperation op = new WebServiceOperation(); + op.setName("sum"); + op.setServiceMethodName("add"); + op.setInput(input); + op.setOutput(output); + + assertThat(op.getImports()).containsExactly( + "org.springframework.ws.server.endpoint.annotation.PayloadRoot", + "org.springframework.ws.server.endpoint.annotation.RequestPayload", + "org.springframework.ws.server.endpoint.annotation.ResponsePayload", + "org.superbiz.wsdl.Sum", + "org.superbiz.wsdl.SumResponse" + ); + + assertThat(op.createSnippet("calculator", "NAMESPACE_URI")).isEqualTo("" + + "@PayloadRoot(namespace = NAMESPACE_URI, localPart = \"sum\")\n" + + "@ResponsePayload\n" + + "public SumResponse sum(@RequestPayload Sum request) {\n" + + "\tSumResponse response = new SumResponse();\n" + + "\tresponse.setReturn(calculator.add(request.getP(), request.getArg1()));\n" + + "\treturn response;\n" + + "}\n" + ); + } + + @Test + void oneWay() { + WebServiceOperation.Type input = new WebServiceOperation.Type(); + input.setSimpleName("Sum"); + input.setPackageName("org.superbiz.wsdl"); + input.setFields(new String[]{"p", "arg1"}); + + WebServiceOperation op = new WebServiceOperation(); + op.setName("sum"); + op.setServiceMethodName("add"); + op.setInput(input); + + assertThat(op.getImports()).containsExactly( + "org.springframework.ws.server.endpoint.annotation.PayloadRoot", + "org.springframework.ws.server.endpoint.annotation.RequestPayload", + "org.superbiz.wsdl.Sum" + ); + + assertThat(op.createSnippet("calculator", "NAMESPACE_URI")).isEqualTo("" + + "@PayloadRoot(namespace = NAMESPACE_URI, localPart = \"sum\")\n" + + "public void sum(@RequestPayload Sum request) {\n" + + "\tcalculator.add(request.getP(), request.getArg1());\n" + + "}\n" + ); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/AddJmsConfigTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/AddJmsConfigTest.java new file mode 100644 index 000000000..72c99890f --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/AddJmsConfigTest.java @@ -0,0 +1,193 @@ +/* + * Copyright 2021 - 2023 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.jms; + +import org.springframework.sbm.jee.jms.actions.AddJmsConfigAction; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; +import freemarker.cache.FileTemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.Version; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AddJmsConfigTest { + + + private final AddJmsConfigAction sut = new AddJmsConfigAction(); + private Configuration configuration; + + @BeforeEach + void setUp() throws IOException { + Version version = new Version("2.3.0"); + configuration = new Configuration(version); + configuration.setTemplateLoader(new FileTemplateLoader(new File("./src/main/resources/templates"))); + sut.setConfiguration(configuration); + } + + @Test + void testAddJmsConfig() { + + String javaSource = + """ + package com.example.foo; + import javax.ejb.MessageDriven; + import javax.jms.Message; + import javax.annotation.Resource; + import javax.jms.Queue; + + @MessageDriven + public class CargoHandled { + + @Resource(name = "ChatBean") + private Queue questionQueue; + + @Resource(name = "AnswerQueue") + private Queue answerQueue; + } + """; + + String expected = + """ + package com.example.foo; + + import javax.jms.ConnectionFactory; + + import javax.jms.Queue; + import org.apache.activemq.command.ActiveMQQueue; + import javax.jms.JMSException; + + import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.jms.annotation.EnableJms; + import org.springframework.jms.config.DefaultJmsListenerContainerFactory; + import org.springframework.jms.config.JmsListenerContainerFactory; + + @Configuration + @EnableJms + public class JmsConfig { + + @Bean + public JmsListenerContainerFactory jmsListenerContainerFactory( + ConnectionFactory connectionFactory, + DefaultJmsListenerContainerFactoryConfigurer configurer) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + configurer.configure(factory, connectionFactory); + return factory; + } + + @Bean + Queue answerQueue(ConnectionFactory connectionFactory) throws JMSException { + ActiveMQQueue activeMQQueue = new ActiveMQQueue("AnswerQueue"); + return activeMQQueue; + } + + @Bean + Queue questionQueue(ConnectionFactory connectionFactory) throws JMSException { + ActiveMQQueue activeMQQueue = new ActiveMQQueue("ChatBean"); + return activeMQQueue; + } + + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax:javaee-api:7.0") + .withJavaSources(javaSource) + .build(); + + sut.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(1).getResource().print(); + assertThat(actual.replace("\r\n", "\n").replace("\r", "\n")) + .as(TestDiff.of(actual, expected)) + .isEqualToNormalizingNewlines(expected); + } + + @Test + void testAddJmsConfigNoQueues() { + + String javaSource = + """ + package the.pckg.name; + + import javax.ejb.MessageDriven; + import javax.jms.Message; + import javax.annotation.Resource; + import javax.jms.Queue; + + @MessageDriven + public class CargoHandled { + + private Queue questionQueue; + + @Resource(name = "AnswerQueue") + private String answerQueue; + } + """; + + String expected = + """ + package the.pckg.name; + + import javax.jms.ConnectionFactory; + + + import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.jms.annotation.EnableJms; + import org.springframework.jms.config.DefaultJmsListenerContainerFactory; + import org.springframework.jms.config.JmsListenerContainerFactory; + + @Configuration + @EnableJms + public class JmsConfig { + + @Bean + public JmsListenerContainerFactory jmsListenerContainerFactory( + ConnectionFactory connectionFactory, + DefaultJmsListenerContainerFactoryConfigurer configurer) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + configurer.configure(factory, connectionFactory); + return factory; + } + + } + """; + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax:javaee-api:7.0") + .withJavaSources(javaSource) + .build(); + + sut.apply(projectContext); + + String actual = projectContext.getProjectJavaSources().list().get(1).getResource().print(); + assertThat(actual.replace("\r\n", "\n").replace("\r", "\n")) + .as(TestDiff.of(actual, expected)) + .isEqualToNormalizingNewlines(expected); + } + + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java new file mode 100644 index 000000000..54a445cb4 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021 - 2023 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.jms; + +import org.springframework.sbm.test.JavaMigrationActionTestSupport; +import org.springframework.sbm.jee.jms.actions.ReplaceMdbAnnotationWithJmsListener; +import org.junit.jupiter.api.Test; + +public class ReplaceMdbAnnotationWithJmsListenerTest { + + @Test + void testReplaceMdbAnnotation() { + + String given = + "import javax.ejb.ActivationConfigProperty;\n" + + "import javax.ejb.MessageDriven;\n" + + "import javax.jms.Message;\n" + + "import javax.jms.MessageListener;\n" + + "\n" + + "@MessageDriven(activationConfig = {\n" + + " @ActivationConfigProperty(propertyName = \"destinationType\", \n" + + " propertyValue = \"javax.jms.Queue\"),\n" + + " @ActivationConfigProperty(propertyName = \"destinationLookup\", \n" + + " propertyValue = \"java:app/jms/CargoHandledQueue\")\n" + + "})\n" + + "public class CargoHandledConsumer implements MessageListener {\n" + + "\n" + + " @Override\n" + + " public void onMessage(Message message) {\n" + + " }\n" + + "}\n" + + ""; + + String expected = + "import org.springframework.jms.annotation.JmsListener;\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "import javax.jms.Message;\n" + + "\n" + + "@Component\n" + + "public class CargoHandledConsumer {\n" + + "\n" + + " @JmsListener(destination = \"CargoHandledQueue\")\n" + + " public void onMessage(Message message) {\n" + + " }\n" + + "}\n" + + ""; + + JavaMigrationActionTestSupport.verify(given, expected, new ReplaceMdbAnnotationWithJmsListener(), + "javax:javaee-api:7.0", + "org.springframework:spring-jms:4.1.7.RELEASE", + "org.springframework:spring-context:4.1.7.RELEASE"); //, "org.springframework:spring-webmvc:4.1.7.RELEASE"); + } + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/AddJoinfacesDependencies_Mojarra_Test.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/AddJoinfacesDependencies_Mojarra_Test.java new file mode 100644 index 000000000..bd0a3bdec --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/AddJoinfacesDependencies_Mojarra_Test.java @@ -0,0 +1,83 @@ +/* + * Copyright 2021 - 2023 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.jsf.recipes; + +import org.springframework.sbm.build.migration.actions.OpenRewriteMavenBuildFileTestSupport; +import org.springframework.sbm.jee.jsf.actions.AddJoinfacesDependencies; +import org.junit.jupiter.api.Test; + +class AddJoinfacesDependencies_Mojarra_Test { + + @Test + void apply() { + String givenPom = + "\n" + + "\n" + + " 4.0.0\n" + + " org.superbiz.jsf\n" + + " jsf-managedBean-and-ejb\n" + + " jar\n" + + " 8.0.5-SNAPSHOT\n" + + " \n" + + " \n" + + " org.glassfish\n" + + " javax.faces\n" + + " 2.2.13\n" + + " jar\n" + + " \n" + + " \n" + + "\n"; + + String expectedPom = + "\n" + + "\n" + + " 4.0.0\n" + + " org.superbiz.jsf\n" + + " jsf-managedBean-and-ejb\n" + + " jar\n" + + " 8.0.5-SNAPSHOT\n" + + " \n" + + " \n" + + " org.glassfish\n" + + " javax.faces\n" + + " 2.2.13\n" + + " jar\n" + + " \n" + + " \n" + + " org.joinfaces\n" + + " jsf-spring-boot-starter\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " org.joinfaces\n" + + " joinfaces-dependencies\n" + + " 4.4.10\n" + + " pom\n" + + " import\n" + + " \n" + + " \n" + + " \n" + + "\n"; + + AddJoinfacesDependencies sut = new AddJoinfacesDependencies(); + + OpenRewriteMavenBuildFileTestSupport.verifyRefactoring(givenPom, expectedPom, sut); + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/AddJoinfacesDependencies_MyFaces_Test.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/AddJoinfacesDependencies_MyFaces_Test.java new file mode 100644 index 000000000..e02f2f976 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/AddJoinfacesDependencies_MyFaces_Test.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 - 2023 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.jsf.recipes; + +import org.springframework.sbm.build.migration.actions.OpenRewriteMavenBuildFileTestSupport; +import org.springframework.sbm.jee.jsf.actions.AddJoinfacesDependencies; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class AddJoinfacesDependencies_MyFaces_Test { + + @Test + @Disabled("excludes currently don't work, see https://rewriteoss.slack.com/archives/G01J94KRH70/p1613504905005900") + void apply() { + String givenPom = + "\n" + + "\n" + + " 4.0.0\n" + + " org.superbiz.jsf\n" + + " jsf-managedBean-and-ejb\n" + + " jar\n" + + " 8.0.5-SNAPSHOT\n" + + " \n" + + " \n" + + " org.apache.myfaces.core\n" + + " myfaces-api\n" + + " 2.1.16\n" + + " provided\n" + + " \n" + + " \n" + + "\n"; + + String expectedPom = + "\n" + + "\n" + + " 4.0.0\n" + + " org.superbiz.jsf\n" + + " jsf-managedBean-and-ejb\n" + + " jar\n" + + " 8.0.5-SNAPSHOT\n" + + " \n" + + " \n" + + " org.apache.myfaces.core\n" + + " myfaces-api\n" + + " 2.1.16\n" + + " provided\n" + + " \n" + + " \n" + + " org.joinfaces\n" + + " jsf-spring-boot-starter\n" + + " \n" + + " \n" + + " org.joinfaces\n" + + " mojarra-spring-boot-starter\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " org.joinfaces\n" + + " myfaces-spring-boot-starter\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " org.joinfaces\n" + + " joinfaces-dependencies\n" + + " 4.4.2\n" + + " pom\n" + + " import\n" + + " \n" + + " \n" + + " \n" + + "\n"; + + AddJoinfacesDependencies sut = new AddJoinfacesDependencies(); + + OpenRewriteMavenBuildFileTestSupport.verifyRefactoring(givenPom, expectedPom, sut); + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/MigrateJsf2xToSpringBootRecipeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/MigrateJsf2xToSpringBootRecipeTest.java new file mode 100644 index 000000000..0fa463bd2 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/recipes/MigrateJsf2xToSpringBootRecipeTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 - 2023 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.jsf.recipes; + +import org.springframework.sbm.test.RecipeTestSupport; +import org.springframework.sbm.build.migration.actions.AddMavenPlugin; +import org.springframework.sbm.build.migration.actions.RemoveDependenciesMatchingRegex; +import org.springframework.sbm.common.migration.actions.MoveFilesAction; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.jee.jsf.actions.AddJoinfacesDependencies; +import org.springframework.sbm.jee.jsf.conditions.IsMigrateJsf2ToSpringBootApplicableCondition; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MigrateJsf2xToSpringBootRecipeTest { + + @Test + void recipe() { + RecipeTestSupport.testRecipe(Path.of("recipes/migrate-jsf-2.x-to-spring-boot.yaml"), recipes -> { + + Optional recipe = recipes.getRecipeByName("migrate-jsf-2.x-to-spring-boot"); + RecipeTestSupport.assertThatRecipeExists(recipe); + + RecipeTestSupport.assertThatRecipeHasCondition(recipe, IsMigrateJsf2ToSpringBootApplicableCondition.class); + + RecipeTestSupport.assertThatRecipeHasActions(recipe, + AddMavenPlugin.class, + RemoveDependenciesMatchingRegex.class, + AddJoinfacesDependencies.class, + RemoveDependenciesMatchingRegex.class, + MoveFilesAction.class, + MoveFilesAction.class, + MoveFilesAction.class, + MoveFilesAction.class + ); + + AddMavenPlugin addMavenPlugin = RecipeTestSupport.getAction(recipe, AddMavenPlugin.class); + assertThat(addMavenPlugin.getPlugin().getGroupId()).isEqualTo("org.joinfaces"); + assertThat(addMavenPlugin.getPlugin().getArtifactId()).isEqualTo("joinfaces-maven-plugin"); + assertThat(addMavenPlugin.getPlugin().getExecutions()).hasSize(1); + assertThat(addMavenPlugin.getPlugin().getExecutions().get(0).getGoals()).containsExactly("classpath-scan"); + }); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java new file mode 100644 index 000000000..cae4079f2 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2021 - 2023 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.tx.actions; + +import org.springframework.sbm.test.JavaMigrationActionTestSupport; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.Method; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MigrateJeeTransactionsToSpringBootActionTest { + + @Test + void migrateTransactionAnnotations() { + String given = + "import javax.ejb.*;\n" + + "@TransactionManagement(TransactionManagementType.CONTAINER)\n" + + "@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)\n" + + "public class TransactionalService {\n" + + " public void requiresNewFromType() {}\n" + + " @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)\n" + + " public void notSupported() {}\n" + + " @TransactionAttribute(TransactionAttributeType.MANDATORY)\n" + + " public void mandatory() {}\n" + + " @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)\n" + + " public void requiresNew() {}\n" + + " @TransactionAttribute(TransactionAttributeType.REQUIRED)\n" + + " public void required() {}\n" + + " @TransactionAttribute(TransactionAttributeType.NEVER)\n" + + " public void never() {}\n" + + " @TransactionAttribute(TransactionAttributeType.SUPPORTS)\n" + + " public void supports() {}\n" + + "}"; + + String expected = + "import org.springframework.transaction.annotation.Propagation;\n" + + "import org.springframework.transaction.annotation.Transactional;\n" + + "\n" + + "\n" + + "@Transactional(propagation = Propagation.REQUIRES_NEW)\n" + + "public class TransactionalService {\n" + + " public void requiresNewFromType() {}\n" + + "\n" + + " @Transactional(propagation = Propagation.NOT_SUPPORTED)\n" + + " public void notSupported() {}\n" + + "\n" + + " @Transactional(propagation = Propagation.MANDATORY)\n" + + " public void mandatory() {}\n" + + "\n" + + " @Transactional(propagation = Propagation.REQUIRES_NEW)\n" + + " public void requiresNew() {}\n" + + "\n" + + " @Transactional(propagation = Propagation.REQUIRED)\n" + + " public void required() {}\n" + + "\n" + + " @Transactional(propagation = Propagation.NEVER)\n" + + " public void never() {}\n" + + "\n" + + " @Transactional(propagation = Propagation.SUPPORTS)\n" + + " public void supports() {}\n" + + "}"; + + MigrateJeeTransactionsToSpringBootAction sut = new MigrateJeeTransactionsToSpringBootAction(); + + JavaMigrationActionTestSupport.verify(given, expected, sut, "javax.ejb:javax.ejb-api:3.2", "org.springframework.boot:spring-boot-starter-data-jpa:2.4.2"); + } + + @Test + void transformMethodAnnotations() { + String given = + "import javax.ejb.*;\n" + + "public class TransactionalService {\n" + + " @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)\n" + + " public void notSupported() {}\n" + + "}"; + + String expected = + "import org.springframework.transaction.annotation.Propagation;\n" + + "import org.springframework.transaction.annotation.Transactional;\n" + + "\n" + + "public class TransactionalService {\n" + + " @Transactional(propagation = Propagation.NOT_SUPPORTED)\n" + + " public void notSupported() {}\n" + + "}"; + + MigrateJeeTransactionsToSpringBootAction sut = new MigrateJeeTransactionsToSpringBootAction(); + + ProjectContext projectContext = TestProjectContext.buildProjectContext() + .withBuildFileHavingDependencies("javax.ejb:javax.ejb-api:3.2", "org.springframework.boot:spring-boot-starter-data-jpa:2.4.2") + .withJavaSources(given) + .build(); + + JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); + Method m = javaSource.getTypes().get(0).getMethods().get(0); + sut.transformMethodAnnotations(m); + + assertThat(javaSource.print()).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/recipes/MigrateTxToSpringBootRecipeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/recipes/MigrateTxToSpringBootRecipeTest.java new file mode 100644 index 000000000..6dd04c310 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/recipes/MigrateTxToSpringBootRecipeTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021 - 2023 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.tx.recipes; + +import org.springframework.sbm.test.RecipeTestSupport; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.java.migration.conditions.HasAnyTypeReference; +import org.springframework.sbm.java.migration.conditions.HasImportStartingWith; +import org.springframework.sbm.jee.tx.actions.MigrateJeeTransactionsToSpringBootAction; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MigrateTxToSpringBootRecipeTest { + + @Test + void testRecipe() { + RecipeTestSupport.testRecipe(Path.of("recipes/migrate-tx-to-spring-boot.yaml"), recipes -> { + Optional recipe = recipes.getRecipeByName("migrate-tx-to-spring-boot"); + RecipeTestSupport.assertThatRecipeExists(recipe); + RecipeTestSupport.assertThatRecipeHasCondition(recipe, HasImportStartingWith.class); + HasImportStartingWith recipeCondition = RecipeTestSupport.getConditionFor(recipe, HasImportStartingWith.class); + assertThat(recipeCondition.getValue()).isEqualTo("javax.ejb.TransactionAttribute"); + + RecipeTestSupport.assertThatRecipeHasActions(recipe, MigrateJeeTransactionsToSpringBootAction.class); + MigrateJeeTransactionsToSpringBootAction action = RecipeTestSupport.getAction(recipe, MigrateJeeTransactionsToSpringBootAction.class); + HasAnyTypeReference actionCondition = RecipeTestSupport.getConditionFor(action, HasAnyTypeReference.class); + assertThat(actionCondition.getFqTypeNames()).contains( + "javax.ejb.TransactionAttribute", + "javax.ejb.TransactionAttributeType", + "javax.ejb.TransactionManagement", + "javax.ejb.TransactionManagementType" + ); + }); + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/web/conditions/ShouldAddServletComponentScanTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/web/conditions/ShouldAddServletComponentScanTest.java new file mode 100644 index 000000000..5479db6a0 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/web/conditions/ShouldAddServletComponentScanTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2021 - 2023 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.web.conditions; + +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.Condition; +import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +class ShouldAddServletComponentScanTest { + + @ParameterizedTest + @CsvSource({ + "true,true,true,false", + "true,true,false,false", + "true,false,true,true", + "false,true,true,false" + }) + void shouldReturnTrueIfAllConditionsAreMet(boolean isSpringBootApplication, boolean isNotAnnotatedWithServletScanComponent, boolean hasWebComponentAnnotation, boolean expected) { + ProjectContext context = TestProjectContext.buildProjectContext().build(); + ShouldAddServletComponentScan sut = spy(new ShouldAddServletComponentScan()); + + Condition isSpringBootApplicationCondition = mock(HasTypeAnnotation.class); + Condition isNotAnnotatedWithServletScanComponentCondition = mock(HasTypeAnnotation.class); + Condition hasWebComponentAnnotationCondition = mock(HasTypeAnnotation.class); + + doReturn(isSpringBootApplicationCondition).when(sut).createIsSpringBootApplicationCondition(); + doReturn(isNotAnnotatedWithServletScanComponentCondition).when(sut).createAnnotatedWithServletScanComponentCondition(); + doReturn(List.of(hasWebComponentAnnotationCondition)).when(sut).createHasWebComponentAnnotation(); + + when(isSpringBootApplicationCondition.evaluate(context)).thenReturn(isSpringBootApplication); + when(isNotAnnotatedWithServletScanComponentCondition.evaluate(context)).thenReturn(isNotAnnotatedWithServletScanComponent); + when(hasWebComponentAnnotationCondition.evaluate(context)).thenReturn(hasWebComponentAnnotation); + + assertThat(sut.evaluate(context)).isEqualTo(expected); + } + + @Test + void testCreateIsSpringBootApplicationCondition() { + ShouldAddServletComponentScan sut = new ShouldAddServletComponentScan(); + + HasTypeAnnotation condition = sut.createIsSpringBootApplicationCondition(); + + assertThat(condition.getAnnotation()) + .isEqualTo("org.springframework.boot.autoconfigure.SpringBootApplication"); + } + + @Test + void testCreateAnnotatedWithServletScanComponentCondition() { + ShouldAddServletComponentScan sut = new ShouldAddServletComponentScan(); + + HasTypeAnnotation condition = sut.createAnnotatedWithServletScanComponentCondition(); + + assertThat(condition.getAnnotation()) + .isEqualTo("org.springframework.boot.web.servlet.ServletComponentScan"); + } + + @Test + void testCreateHasWebComponentAnnotationTest() { + ShouldAddServletComponentScan sut = new ShouldAddServletComponentScan(); + + List conditions = sut.createHasWebComponentAnnotation(); + + assertThat(conditions) + .extracting(HasTypeAnnotation::getAnnotation) + .contains("javax.servlet.annotation.WebListener", + "javax.servlet.annotation.WebServlet", + "javax.servlet.annotation.WebFilter"); + } + +} \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/web/recipes/MigrateAnnotatedServletRecipeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/web/recipes/MigrateAnnotatedServletRecipeTest.java new file mode 100644 index 000000000..d8646d759 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/web/recipes/MigrateAnnotatedServletRecipeTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 - 2023 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.web.recipes; + +import org.springframework.sbm.build.migration.actions.AddDependencies; +import org.springframework.sbm.build.migration.actions.RemoveDependenciesMatchingRegex; +import org.springframework.sbm.engine.recipe.Recipe; +import org.springframework.sbm.java.migration.actions.AddTypeAnnotationToTypeAnnotatedWith; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.Optional; + +import static org.springframework.sbm.test.RecipeTestSupport.*; +import static org.assertj.core.api.Assertions.assertThat; + +public class MigrateAnnotatedServletRecipeTest { + + @Test + void testMigrateAnnotatedServletRecipe() { + + Path recipePath = Path.of("recipes/migrate-annotated-servlets.yaml"); + + testRecipe(recipePath, recipes -> { + + Optional recipe = recipes.getRecipeByName("migrate-annotated-servlets"); + + assertThatRecipeExists(recipe); + assertThatRecipeHasActions(recipe, + AddDependencies.class, + AddTypeAnnotationToTypeAnnotatedWith.class, + RemoveDependenciesMatchingRegex.class + ); + + AddDependencies addDeps = getAction(recipe, AddDependencies.class); + assertThat(addDeps.getDependencies()) + .allMatch(d -> + d.getGroupId().equals("org.springframework.boot") && + d.getArtifactId().equals("spring-boot-starter-web") + ); + + AddTypeAnnotationToTypeAnnotatedWith addTypeAnnotationToTypeAnnotatedWith = getAction(recipe, AddTypeAnnotationToTypeAnnotatedWith.class); + assertThat(addTypeAnnotationToTypeAnnotatedWith.getAnnotation()).isEqualTo("org.springframework.boot.web.servlet.ServletComponentScan"); + assertThat(addTypeAnnotationToTypeAnnotatedWith.getAnnotatedWith()).isEqualTo("org.springframework.boot.autoconfigure.SpringBootApplication"); + }); + } +} diff --git a/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/pom.xml b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/pom.xml new file mode 100644 index 000000000..c7985a4cc --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + org.example + eclipselink-jpa + 1.0-SNAPSHOT + + 11 + 11 + + + + org.eclipse.persistence + org.eclipse.persistence.jpa + 2.7.10 + + + \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/src/main/java/com/example/SomeClass.java b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/src/main/java/com/example/SomeClass.java new file mode 100644 index 000000000..837c8b4aa --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/src/main/java/com/example/SomeClass.java @@ -0,0 +1,3 @@ +package com.example; + +public class SomeClass {} \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/src/main/resources/META-INF/persistence.xml b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/src/main/resources/META-INF/persistence.xml new file mode 100644 index 000000000..2906d3c12 --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/expected/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,122 @@ + + + org.eclipse.persistence.jpa.PersistenceProvider + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/pom.xml b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/pom.xml new file mode 100644 index 000000000..c7985a4cc --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + org.example + eclipselink-jpa + 1.0-SNAPSHOT + + 11 + 11 + + + + org.eclipse.persistence + org.eclipse.persistence.jpa + 2.7.10 + + + \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/src/main/java/com/example/SomeClass.java b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/src/main/java/com/example/SomeClass.java new file mode 100644 index 000000000..837c8b4aa --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/src/main/java/com/example/SomeClass.java @@ -0,0 +1,3 @@ +package com.example; + +public class SomeClass {} \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/src/main/resources/META-INF/persistence.xml b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/src/main/resources/META-INF/persistence.xml new file mode 100644 index 000000000..2906d3c12 --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jpa/eclipselink-jpa/given/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,122 @@ + + + org.eclipse.persistence.jpa.PersistenceProvider + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/jee/web/given/pom.xml b/components/jaxrs-recipes/testcode/jee/web/given/pom.xml new file mode 100644 index 000000000..0a0b3fafe --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/web/given/pom.xml @@ -0,0 +1,38 @@ + + + + + 4.0.0 + com.example.jee + web + 0.1.0-SNAPSHOT + + + 11 + 11 + + + + + javax.servlet + javax.servlet-api + 4.0.0 + provided + + + \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/jee/web/given/src/main/java/com/examples/jee/web/TheServlet.java b/components/jaxrs-recipes/testcode/jee/web/given/src/main/java/com/examples/jee/web/TheServlet.java new file mode 100644 index 000000000..5bd0a736c --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/web/given/src/main/java/com/examples/jee/web/TheServlet.java @@ -0,0 +1,15 @@ +package com.examples.jee.web; + +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServlet; +import java.io.IOException; +import javax.servlet.annotation.WebServlet; + +@WebServlet(name = "The Servlet", urlPatterns = {"/foo", "/bar"}) +public class TheServlet extends HttpServlet { + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + + } +} \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/jee/web/given/src/main/webapp/WEB-INF/web.xml b/components/jaxrs-recipes/testcode/jee/web/given/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..4b79dab8f --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/web/given/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,32 @@ + + + + + + TheServlet + com.examples.jee.web.TheServlet + + + TheServlet + /foo + /bar + + \ No newline at end of file diff --git a/components/jaxrs-recipes/testcode/wsdl/calculator.wsdl b/components/jaxrs-recipes/testcode/wsdl/calculator.wsdl new file mode 100644 index 000000000..fbf6f08df --- /dev/null +++ b/components/jaxrs-recipes/testcode/wsdl/calculator.wsdl @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e29a09efe0663c7fe1d74391a6a25bae56d142c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 08:59:08 +0200 Subject: [PATCH 03/19] Execute SBM jax-rs recipe through OpenRewrite --- components/jaxrs-recipes/pom.xml | 69 ++-- .../java/example/recipe/SbmAdapterRecipe.java | 190 +++++++--- .../recipes/RewriteMethodInvocation.java | 12 +- .../ReplaceConstantWithAnotherConstant.java | 24 +- .../jee/ejb/actions/MigrateJndiLookup.java | 21 +- .../sbm/jee/jaxrs/MigrateJaxRsRecipe.java | 6 +- .../actions/ConvertJaxRsAnnotations.java | 324 +++++++++++++++++- .../recipes/CopyAnnotationAttribute.java | 6 +- .../RemoveAnnotationIfAccompanied.java | 4 +- .../jee/jaxrs/recipes/ReplaceMediaType.java | 46 ++- .../ReplaceRequestParameterProperties.java | 30 +- .../recipes/ReplaceResponseEntityBuilder.java | 109 +++--- .../jee/jaxrs/recipes/SwapCacheControl.java | 30 +- .../jaxrs/recipes/SwapFamilyForSeries.java | 27 +- .../sbm/jee/jaxrs/recipes/SwapHttHeaders.java | 57 +-- .../SwapResponseWithResponseEntity.java | 196 ++++++----- .../recipes/SwapStatusForHttpStatus.java | 34 +- .../CopyAnnotationAttributeVisitor.java | 2 +- .../sbm/jee/jaxws/WebServiceDescriptor.java | 17 +- .../sbm/JaxRsThroughAdapterTest.java | 254 ++++++++++++++ .../jaxrs/recipes/ReplaceMediaTypeTest.java | 9 +- .../ResponseEntityReplacementTest.java | 8 +- .../jee/jaxrs/recipes/ResponseStatusTest.java | 3 +- .../jee/jaxrs/bootify-jaxrs/given/pom.xml | 44 +++ .../com/example/jee/app/PersonController.java | 45 +++ .../springframework/sbm/java/api/Method.java | 3 + .../sbm/java/api/MethodParam.java | 1 + .../sbm/java/api/ProjectJavaSources.java | 2 +- .../springframework/sbm/java/api/Type.java | 2 + .../sbm/java/impl/CompiledType.java | 5 + .../sbm/java/impl/OpenRewriteMethod.java | 40 ++- .../sbm/java/impl/OpenRewriteMethodParam.java | 1 + .../sbm/java/impl/OpenRewriteType.java | 35 +- .../sbm/java/impl/ProjectJavaSourcesImpl.java | 2 +- .../java/AddAnnotationVisitor.java | 6 +- 35 files changed, 1283 insertions(+), 381 deletions(-) create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java create mode 100644 components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml create mode 100644 components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/src/main/java/com/example/jee/app/PersonController.java diff --git a/components/jaxrs-recipes/pom.xml b/components/jaxrs-recipes/pom.xml index adb16aad8..573b90406 100644 --- a/components/jaxrs-recipes/pom.xml +++ b/components/jaxrs-recipes/pom.xml @@ -19,12 +19,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - - - - - org.springframework.sbm jaxrs-recipes 0.15.2-SNAPSHOT @@ -68,6 +62,13 @@ + + + org.openrewrite.maven + rewrite-maven-plugin + 5.20.0 + + org.openrewrite @@ -80,6 +81,13 @@ ${openrewrite.version} + + org.springframework.rewrite + spring-rewrite-commons-plugin-invoker-polyglot + 0.1.0-SNAPSHOT + test + + org.springframework.sbm @@ -119,43 +127,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.aspectj @@ -218,6 +189,18 @@ 8.29.0 compile + + org.openrewrite.recipe + rewrite-static-analysis + 1.11.0 + compile + + + org.openrewrite + rewrite-test + 8.29.0 + test + diff --git a/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java index 49a01a143..8d4b774a1 100644 --- a/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java +++ b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java @@ -16,16 +16,13 @@ package example.recipe; -import org.jetbrains.annotations.NotNull; import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; import org.springframework.rewrite.parser.JavaParserBuilder; -import org.springframework.rewrite.resource.ProjectResourceSet; -import org.springframework.rewrite.resource.ProjectResourceSetFactory; -import org.springframework.rewrite.resource.RewriteMigrationResultMerger; -import org.springframework.rewrite.resource.RewriteSourceFileWrapper; +import org.springframework.rewrite.resource.*; import org.springframework.sbm.engine.context.ProjectContext; import org.springframework.sbm.engine.context.ProjectContextFactory; +import org.springframework.sbm.java.JavaSourceProjectResourceWrapper; import org.springframework.sbm.java.refactoring.JavaRefactoringFactory; import org.springframework.sbm.java.refactoring.JavaRefactoringFactoryImpl; import org.springframework.sbm.java.util.BasePackageCalculator; @@ -37,14 +34,16 @@ import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** * @author Fabian Krüger */ -public class SbmAdapterRecipe extends Recipe { - +public class SbmAdapterRecipe extends ScanningRecipe> { + private ProjectResourceSetFactory projectResourceSetFactory; + private ProjectContextFactory projectContextFactory; @Override public @NlsRewrite.DisplayName String getDisplayName() { @@ -61,63 +60,158 @@ public SbmAdapterRecipe() { } @Override - public TreeVisitor getVisitor() { + public List getInitialValue(ExecutionContext ctx) { + return new ArrayList<>(); + } + + @Override + public TreeVisitor getScanner(List acc) { return new TreeVisitor() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext) { + if(tree instanceof SourceFile) { + acc.add((SourceFile) tree); + } + return super.visit(tree, executionContext); + } + }; + } - private ProjectResourceSetFactory projectResourceSetFactory; - private ProjectContextFactory projectContextFactory; + @Override + public Collection generate(List acc, ExecutionContext executionContext) { + Collection generate = super.generate(acc, executionContext); - @Override - public void visit(@Nullable List nodes, ExecutionContext executionContext) { + // create the required classes + initBeans(executionContext); - // create the required classes - initBeans(executionContext); + // transform nodes to SourceFiles + List sourceFiles = acc.stream().filter(SourceFile.class::isInstance).map(SourceFile.class::cast).toList(); - // transform nodes to SourceFiles - List sourceFiles = nodes.stream().filter(SourceFile.class::isInstance).map(SourceFile.class::cast).toList(); + // FIXME: base dir calculation is fake + Path baseDir = Path.of("/Users/fkrueger/projects/spring-boot-migrator/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given").toAbsolutePath().normalize(); //executionContext.getMessage("base.dir"); - // FIXME: base dir calculation is fake - Path baseDir = executionContext.getMessage("base.dir"); + // Create the SBM resource set abstraction + ProjectResourceSet projectResourceSet = projectResourceSetFactory.create(baseDir, sourceFiles); + // Create the SBM ProjectContext + ProjectContext pc = projectContextFactory.createProjectContext(baseDir, projectResourceSet); - // Create the SBM resource set abstraction - ProjectResourceSet projectResourceSet = projectResourceSetFactory.create(baseDir, sourceFiles); - // Create the SBM ProjectContext - ProjectContext pc = projectContextFactory.createProjectContext(baseDir, projectResourceSet); + // Execute the SBM Action = the JAXRS Recipe + new ConvertJaxRsAnnotations().apply(pc); - // Execute the SBM Action = the JAXRS Recipe - new ConvertJaxRsAnnotations().apply(pc); + // Merge back result + List modifiedNodes = merge(sourceFiles, pc.getProjectResources()); - // Merge back result - List modifiedNodes = merge(nodes, pc.getProjectResources()); + return modifiedNodes; + } - // Process other - super.visit(modifiedNodes, executionContext); - } + // @Override +// public List getRecipeList() { +// List recipeList = new ArrayList<>(); +// recipeList.add(new GenericOpenRewriteRecipe<>(() -> new TreeVisitor() { +// @Override +// public void visit(@Nullable List nodes, ExecutionContext executionContext) { +// super.visit(nodes, executionContext); +// } +// +// @Override +// public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext) { +// return super.visit(tree, executionContext); +// } +// +// @Override +// public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext, Cursor parent) { +// return super.visit(tree, executionContext, parent); +// } +// })); +// return recipeList; +// } + +// @Override +// public TreeVisitor getVisitor() { +// return new TreeVisitor() { +// +// private ProjectResourceSetFactory projectResourceSetFactory; +// private ProjectContextFactory projectContextFactory; +// +// @Override +// public Tree visitNonNull(Tree tree, ExecutionContext executionContext) { +// return super.visitNonNull(tree, executionContext); +// } +// +// @Override +// public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { +// return true; +// } +// +// @Override +// public void visit(@Nullable List nodes, ExecutionContext executionContext) { +// +// super.visit(nodes, executionContext); +// +// // create the required classes +// initBeans(executionContext); +// +// // transform nodes to SourceFiles +// List sourceFiles = nodes.stream().filter(SourceFile.class::isInstance).map(SourceFile.class::cast).toList(); +// +// // FIXME: base dir calculation is fake +// Path baseDir = executionContext.getMessage("base.dir"); +// +// // Create the SBM resource set abstraction +// ProjectResourceSet projectResourceSet = projectResourceSetFactory.create(baseDir, sourceFiles); +// // Create the SBM ProjectContext +// ProjectContext pc = projectContextFactory.createProjectContext(baseDir, projectResourceSet); +// +// // Execute the SBM Action = the JAXRS Recipe +// new ConvertJaxRsAnnotations().apply(pc); +// +// // Merge back result +// List modifiedNodes = merge(nodes, pc.getProjectResources()); +// +// // Process other +// super.visit(modifiedNodes, executionContext); +// } +// +// }; +// } + + private List merge(List nodes, ProjectResourceSet projectResources) { + // merge the changed results into the given list and return the result + ArrayList merged = new ArrayList<>(); + merged.addAll(nodes); + projectResources.stream() + .filter(r -> r.hasChanges()) + .forEach(changed -> { + int pos = findPosition(merged, changed); + merged.add(pos, changed.getSourceFile()); + }); + return merged; + } - private void initBeans(ExecutionContext executionContext) { - RewriteSourceFileWrapper sourceFileWrapper = new RewriteSourceFileWrapper(); - SbmApplicationProperties sbmApplicationProperties = new SbmApplicationProperties(); - JavaParserBuilder parserBuilder = new JavaParserBuilder(); - List projectResourceWrappers = new ArrayList<>(); + private int findPosition(List merged, RewriteSourceFileHolder changed) { + List paths = merged.stream().map(f -> f.getSourcePath().toString()).toList(); + return paths.indexOf(changed.getSourcePath().toString()); + } - RewriteMigrationResultMerger merger = new RewriteMigrationResultMerger(sourceFileWrapper); - ProjectResourceSetHolder holder = new ProjectResourceSetHolder(executionContext, merger); - projectResourceSetFactory = new ProjectResourceSetFactory(new RewriteMigrationResultMerger(sourceFileWrapper), sourceFileWrapper, executionContext); - ProjectResourceWrapperRegistry registry = new ProjectResourceWrapperRegistry(projectResourceWrappers); - JavaRefactoringFactory refactoringFactory = new JavaRefactoringFactoryImpl(holder, executionContext); - BasePackageCalculator calculator = new BasePackageCalculator(sbmApplicationProperties); + private void initBeans(ExecutionContext executionContext) { + RewriteSourceFileWrapper sourceFileWrapper = new RewriteSourceFileWrapper(); + SbmApplicationProperties sbmApplicationProperties = new SbmApplicationProperties(); + JavaParserBuilder parserBuilder = new JavaParserBuilder(); + List projectResourceWrappers = new ArrayList<>(); + RewriteMigrationResultMerger merger = new RewriteMigrationResultMerger(sourceFileWrapper); + ProjectResourceSetHolder holder = new ProjectResourceSetHolder(executionContext, merger); + JavaRefactoringFactory refactoringFactory = new JavaRefactoringFactoryImpl(holder, executionContext); + projectResourceWrappers.add(new JavaSourceProjectResourceWrapper(refactoringFactory, parserBuilder, executionContext)); - ProjectResourceSetFactory resourceSetFactory = new ProjectResourceSetFactory(merger, sourceFileWrapper, executionContext); - projectContextFactory = new ProjectContextFactory(registry, holder, refactoringFactory, calculator, parserBuilder, executionContext, merger, resourceSetFactory); - } - }; - } + projectResourceSetFactory = new ProjectResourceSetFactory(new RewriteMigrationResultMerger(sourceFileWrapper), sourceFileWrapper, executionContext); + ProjectResourceWrapperRegistry registry = new ProjectResourceWrapperRegistry(projectResourceWrappers); + BasePackageCalculator calculator = new BasePackageCalculator(sbmApplicationProperties); - private List merge(List nodes, ProjectResourceSet projectResources) { - // merge the changed results into the given list and return the result - return new ArrayList<>(); + ProjectResourceSetFactory resourceSetFactory = new ProjectResourceSetFactory(merger, sourceFileWrapper, executionContext); + + projectContextFactory = new ProjectContextFactory(registry, holder, refactoringFactory, calculator, parserBuilder, executionContext, merger, resourceSetFactory); } } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.java index 0aeb36b54..d8007be3c 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.java @@ -17,6 +17,7 @@ import lombok.RequiredArgsConstructor; import org.openrewrite.ExecutionContext; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.AddImport; @@ -38,11 +39,16 @@ public class RewriteMethodInvocation extends Recipe { @Override public String getDisplayName() { - return "Rewritre method invocation"; + return "Rewrite method invocation"; } - + + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + @Override - protected TreeVisitor getVisitor() { + public TreeVisitor getVisitor() { return new JavaVisitor() { @Override diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java index 5b8c8a552..f35ee7045 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java @@ -47,9 +47,11 @@ public String getDescription() { return "Replace constant with another constant, adding/removing import on class if needed."; } - @Override + + + // FIXME: (jaxrs) Call from visitor to decide if applicable protected TreeVisitor getSingleSourceApplicableTest() { - return new UsesType<>(existingFullyQualifiedConstantName.substring(0,existingFullyQualifiedConstantName.lastIndexOf('.'))); + return new UsesType<>(existingFullyQualifiedConstantName.substring(0,existingFullyQualifiedConstantName.lastIndexOf('.')), true); } @Override @@ -79,11 +81,12 @@ public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext executionC maybeRemoveImport(existingOwningType.substring(0, existingOwningType.indexOf('$'))); } maybeAddImport(owningType, false); - return fieldAccess - .withTemplate( - JavaTemplate.builder(this::getCursor, template).imports(owningType).build(), - fieldAccess.getCoordinates().replace()) + + fieldAccess = JavaTemplate.builder(template).imports(owningType).build() + .apply(getCursor(), fieldAccess.getCoordinates().replace()) .withPrefix(fieldAccess.getPrefix()); + + return fieldAccess; } return super.visitFieldAccess(fieldAccess, executionContext); } @@ -93,10 +96,11 @@ public J visitIdentifier(J.Identifier ident, ExecutionContext executionContext) if (isConstant(ident.getFieldType()) && !isVariableDeclaration()) { maybeRemoveImport(existingOwningType); maybeAddImport(owningType, false); - return ident - .withTemplate( - JavaTemplate.builder(this::getCursor, template).imports(owningType).build(), - ident.getCoordinates().replace()) + + return JavaTemplate.builder(template) + .imports(owningType) + .build() + .apply(getCursor(), ident.getCoordinates().replace()) .withPrefix(ident.getPrefix()); } return super.visitIdentifier(ident, executionContext); diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java index 4803f64b9..ee4a1dc87 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java @@ -15,6 +15,7 @@ */ package org.springframework.sbm.jee.ejb.actions; +import org.openrewrite.staticanalysis.RemoveUnusedLocalVariables; import org.springframework.rewrite.recipes.GenericOpenRewriteRecipe; import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.java.api.JavaSource; @@ -27,7 +28,6 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.RemoveUnusedImports; -import org.openrewrite.java.cleanup.RemoveUnusedLocalVariables; import org.openrewrite.java.format.AutoFormat; import org.openrewrite.java.tree.*; @@ -47,13 +47,14 @@ public void apply(ProjectContext context) { } private void migrateJndiLookup(JavaSource sourceWithLookup) { - Recipe recipe = new GenericOpenRewriteRecipe<>(() -> new MigrateJndiLookupVisitor()) - .doNext(new RemoveUnusedLocalVariables(null)) - .doNext(new RemoveUnusedImports()) - .doNext(new GenericOpenRewriteRecipe<>(() -> new AddImport<>("org.springframework.beans.factory.annotation.Autowired", null, false))) - .doNext(new AutoFormat()); - - sourceWithLookup.apply(recipe); + List recipeList = List.of( + new GenericOpenRewriteRecipe<>(() -> new MigrateJndiLookupVisitor()), + new RemoveUnusedLocalVariables(null), + new RemoveUnusedImports(), + new GenericOpenRewriteRecipe<>(() -> new AddImport<>("org.springframework.beans.factory.annotation.Autowired", null, false)), + new AutoFormat() + ); + sourceWithLookup.apply(recipeList.toArray(new Recipe[]{})); } class MigrateJndiLookupVisitor extends JavaIsoVisitor { @@ -140,8 +141,8 @@ private J.ClassDeclaration addInstanceAsAutowiredMember(J.ClassDeclaration class J.VariableDeclarations variable = matchFound.getMultiVariable(); JavaType.Class type = (JavaType.Class) variable.getTypeExpression().getType(); String variableName = variable.getVariables().get(0).getSimpleName(); - JavaTemplate javaTemplate = JavaTemplate.builder(() -> getCursor(), "@Autowired\nprivate " + type.getClassName() + " " + variableName).build(); - J.Block result = body.withTemplate(javaTemplate, body.getCoordinates().lastStatement()); + JavaTemplate javaTemplate = JavaTemplate.builder("@Autowired\nprivate " + type.getClassName() + " " + variableName).build(); + J.Block result = javaTemplate.apply(getCursor(), body.getCoordinates().lastStatement()); List statements1 = result.getStatements(); Statement statement = statements1.get(statements1.size() - 1); statements1.remove(statement); diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java index 724ebbe17..97002c7a5 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java @@ -46,8 +46,6 @@ public class MigrateJaxRsRecipe { - private final Supplier javaParserSupplier = () -> JavaParser.fromJavaVersion().classpath(ClasspathRegistry.getInstance().getCurrentDependencies()).build(); - @Bean public Recipe jaxRs(RewriteRecipeLoader rewriteRecipeLoader) { return Recipe.builder() @@ -99,7 +97,7 @@ public Recipe jaxRs(RewriteRecipeLoader rewriteRecipeLoader) { JavaRecipeAction.builder() .condition(HasImportStartingWith.builder().value("javax.ws.rs.core.MediaType").build()) .description("Replace JaxRs MediaType with it's Spring equivalent.") - .recipe(new ReplaceMediaType(javaParserSupplier)) + .recipe(new ReplaceMediaType()) .build(), JavaRecipeAction.builder() @@ -123,7 +121,7 @@ public Recipe jaxRs(RewriteRecipeLoader rewriteRecipeLoader) { JavaRecipeAction.builder() .condition(HasImportStartingWith.builder().value("javax.ws.rs.core.Response").build()) .description("Replace JaxRs Response and ResponseBuilder with it's Spring equivalent.") - .recipe(new SwapResponseWithResponseEntity(javaParserSupplier)) + .recipe(new SwapResponseWithResponseEntity()) .build(), JavaRecipeAction.builder() diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java index af8649909..5b06f2277 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java @@ -146,7 +146,82 @@ void convertJaxRsMethodToSpringMvc(Method method) { sb.append(")"); } - method.addAnnotation(sb.toString(), "org.springframework.web.bind.annotation.RequestMapping", "org.springframework.web.bind.annotation.RequestMethod"); + Set typeStubs = Set.of( + """ + package org.springframework.web.bind.annotation; + import java.lang.annotation.Documented; + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + import org.springframework.aot.hint.annotation.Reflective; + import org.springframework.core.annotation.AliasFor; + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Mapping + @Reflective(ControllerMappingReflectiveProcessor.class) + public @interface RequestMapping { + String name() default ""; + @AliasFor("path") + String[] value() default {}; + @AliasFor("value") + String[] path() default {}; + RequestMethod[] method() default {}; + String[] params() default {}; + String[] headers() default {}; + String[] consumes() default {}; + String[] produces() default {}; + + } + """, + """ + package org.springframework.web.bind.annotation; + + import org.springframework.http.HttpMethod; + import org.springframework.lang.Nullable; + import org.springframework.util.Assert; + public enum RequestMethod { + + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + @Nullable + public static RequestMethod resolve(String method) { + Assert.notNull(method, "Method must not be null"); + return switch (method) { + case "GET" -> GET; + case "HEAD" -> HEAD; + case "POST" -> POST; + case "PUT" -> PUT; + case "PATCH" -> PATCH; + case "DELETE" -> DELETE; + case "OPTIONS" -> OPTIONS; + case "TRACE" -> TRACE; + default -> null; + }; + } + @Nullable + public static RequestMethod resolve(HttpMethod httpMethod) { + Assert.notNull(httpMethod, "HttpMethod must not be null"); + return resolve(httpMethod.name()); + } + public HttpMethod asHttpMethod() { + return switch (this) { + case GET -> HttpMethod.GET; + case HEAD -> HttpMethod.HEAD; + case POST -> HttpMethod.POST; + case PUT -> HttpMethod.PUT; + case PATCH -> HttpMethod.PATCH; + case DELETE -> HttpMethod.DELETE; + case OPTIONS -> HttpMethod.OPTIONS; + case TRACE -> HttpMethod.TRACE; + }; + } + } + """ + ); + + method.addAnnotation(sb.toString(), "org.springframework.web.bind.annotation.RequestMapping", typeStubs, "org.springframework.web.bind.annotation.RequestMethod"); } } @@ -160,7 +235,30 @@ private void transformTypeAnnotations(Type type) { Optional found = annotations.stream().filter(a -> "javax.ws.rs.Path".equals(a.getFullyQualifiedName())).findFirst(); if (found.isPresent()) { type.removeAnnotation(found.get()); - type.addAnnotation("org.springframework.web.bind.annotation.RestController"); + String fqName = "org.springframework.web.bind.annotation.RestController"; + String snippet = "@" + fqName.substring(fqName.lastIndexOf('.') + 1); + Set stubs = Set.of(""" + package org.springframework.web.bind.annotation; + + import java.lang.annotation.Documented; + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + import org.springframework.core.annotation.AliasFor; + import org.springframework.stereotype.Controller; + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Controller + @ResponseBody + public @interface RestController { + @AliasFor(annotation = Controller.class) + String value() default ""; + } + """); + type.addAnnotation(snippet, "org.springframework.web.bind.annotation.RestController", stubs); Map attributes = new LinkedHashMap<>(found.get().getAttributes()); for (Annotation a : annotations) { if (a == null) { @@ -183,7 +281,227 @@ private void transformTypeAnnotations(Type type) { } } String rmAttrs = attributes.entrySet().stream().map(e -> e.getKey() + " = " + e.getValue().print()).collect(Collectors.joining(", ")); - type.addAnnotation("@RequestMapping(" + rmAttrs + ")", "org.springframework.web.bind.annotation.RequestMapping"); + type.addAnnotation("@RequestMapping(" + rmAttrs + ")", "org.springframework.web.bind.annotation.RequestMapping", Set.of(""" + /* + * Copyright 2002-2024 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.web.bind.annotation; + + import java.lang.annotation.Documented; + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + import org.springframework.aot.hint.annotation.Reflective; + import org.springframework.core.annotation.AliasFor; + + /** + * Annotation for mapping web requests onto methods in request-handling classes + * with flexible method signatures. + * + *

Both Spring MVC and Spring WebFlux support this annotation through a + * {@code RequestMappingHandlerMapping} and {@code RequestMappingHandlerAdapter} + * in their respective modules and package structures. For the exact list of + * supported handler method arguments and return types in each, please use the + * reference documentation links below: + *

+ * + *

NOTE: This annotation can be used both at the class and + * at the method level. In most cases, at the method level applications will + * prefer to use one of the HTTP method specific variants + * {@link GetMapping @GetMapping}, {@link PostMapping @PostMapping}, + * {@link PutMapping @PutMapping}, {@link DeleteMapping @DeleteMapping}, or + * {@link PatchMapping @PatchMapping}. + * + *

NOTE: This annotation cannot be used in conjunction with + * other {@code @RequestMapping} annotations that are declared on the same element + * (class, interface, or method). If multiple {@code @RequestMapping} annotations + * are detected on the same element, a warning will be logged, and only the first + * mapping will be used. This also applies to composed {@code @RequestMapping} + * annotations such as {@code @GetMapping}, {@code @PostMapping}, etc. + * + *

NOTE: When using controller interfaces (e.g. for AOP proxying), + * make sure to consistently put all your mapping annotations — such + * as {@code @RequestMapping} and {@code @SessionAttributes} — on + * the controller interface rather than on the implementation class. + * + * @author Juergen Hoeller + * @author Arjen Poutsma + * @author Sam Brannen + * @since 2.5 + * @see GetMapping + * @see PostMapping + * @see PutMapping + * @see DeleteMapping + * @see PatchMapping + */ + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Mapping + @Reflective(ControllerMappingReflectiveProcessor.class) + public @interface RequestMapping { + + /** + * Assign a name to this mapping. + *

Supported at the type level as well as at the method level! + * When used on both levels, a combined name is derived by concatenation + * with "#" as separator. + * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder + * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy + */ + String name() default ""; + + /** + * The path mapping URIs — for example, {@code "/profile"}. + *

This is an alias for {@link #path}. For example, + * {@code @RequestMapping("/profile")} is equivalent to + * {@code @RequestMapping(path="/profile")}. + *

See {@link #path} for further details. + */ + @AliasFor("path") + String[] value() default {}; + + /** + * The path mapping URIs — for example, {@code "/profile"}. + *

Ant-style path patterns are also supported (e.g. {@code "/profile/**"}). + * At the method level, relative paths (e.g. {@code "edit"}) are supported + * within the primary mapping expressed at the type level. + * Path mapping URIs may contain placeholders (e.g. "/${profile_path}"). + *

Supported at the type level as well as at the method level! + * When used at the type level, all method-level mappings inherit + * this primary mapping, narrowing it for a specific handler method. + *

NOTE: A handler method that is not mapped to any path + * explicitly is effectively mapped to an empty path. + * @since 4.2 + */ + @AliasFor("value") + String[] path() default {}; + + /** + * The HTTP request methods to map to, narrowing the primary mapping: + * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. + *

Supported at the type level as well as at the method level! + * When used at the type level, all method-level mappings inherit this + * HTTP method restriction. + */ + RequestMethod[] method() default {}; + + /** + * The parameters of the mapped request, narrowing the primary mapping. + *

Same format for any environment: a sequence of "myParam=myValue" style + * expressions, with a request only mapped if each such parameter is found + * to have the given value. Expressions can be negated by using the "!=" operator, + * as in "myParam!=myValue". "myParam" style expressions are also supported, + * with such parameters having to be present in the request (allowed to have + * any value). Finally, "!myParam" style expressions indicate that the + * specified parameter is not supposed to be present in the request. + *

Supported at the type level as well as at the method level! + * When used at the type level, all method-level mappings inherit this + * parameter restriction. + */ + String[] params() default {}; + + /** + * The headers of the mapped request, narrowing the primary mapping. + *

Same format for any environment: a sequence of "My-Header=myValue" style + * expressions, with a request only mapped if each such header is found + * to have the given value. Expressions can be negated by using the "!=" operator, + * as in "My-Header!=myValue". "My-Header" style expressions are also supported, + * with such headers having to be present in the request (allowed to have + * any value). Finally, "!My-Header" style expressions indicate that the + * specified header is not supposed to be present in the request. + *

Also supports media type wildcards (*), for headers such as Accept + * and Content-Type. For instance, + *

+                    	 * @RequestMapping(value = "/something", headers = "content-type=text/*")
+                    	 * 
+ * will match requests with a Content-Type of "text/html", "text/plain", etc. + *

Supported at the type level as well as at the method level! + * When used at the type level, all method-level mappings inherit this + * header restriction. + * @see org.springframework.http.MediaType + */ + String[] headers() default {}; + + /** + * Narrows the primary mapping by media types that can be consumed by the + * mapped handler. Consists of one or more media types one of which must + * match to the request {@code Content-Type} header. Examples: + *

+                    	 * consumes = "text/plain"
+                    	 * consumes = {"text/plain", "application/*"}
+                    	 * consumes = MediaType.TEXT_PLAIN_VALUE
+                    	 * 
+ *

If a declared media type contains a parameter, and if the + * {@code "content-type"} from the request also has that parameter, then + * the parameter values must match. Otherwise, if the media type from the + * request {@code "content-type"} does not contain the parameter, then the + * parameter is ignored for matching purposes. + *

Expressions can be negated by using the "!" operator, as in + * "!text/plain", which matches all requests with a {@code Content-Type} + * other than "text/plain". + *

Supported at the type level as well as at the method level! + * If specified at both levels, the method level consumes condition overrides + * the type level condition. + * @see org.springframework.http.MediaType + * @see jakarta.servlet.http.HttpServletRequest#getContentType() + */ + String[] consumes() default {}; + + /** + * Narrows the primary mapping by media types that can be produced by the + * mapped handler. Consists of one or more media types one of which must + * be chosen via content negotiation against the "acceptable" media types + * of the request. Typically those are extracted from the {@code "Accept"} + * header but may be derived from query parameters, or other. Examples: + *

+                    	 * produces = "text/plain"
+                    	 * produces = {"text/plain", "application/*"}
+                    	 * produces = MediaType.TEXT_PLAIN_VALUE
+                    	 * produces = "text/plain;charset=UTF-8"
+                    	 * 
+ *

If a declared media type contains a parameter (e.g. "charset=UTF-8", + * "type=feed", "type=entry") and if a compatible media type from the request + * has that parameter too, then the parameter values must match. Otherwise, + * if the media type from the request does not contain the parameter, it is + * assumed the client accepts any value. + *

Expressions can be negated by using the "!" operator, as in "!text/plain", + * which matches all requests with a {@code Accept} other than "text/plain". + *

Supported at the type level as well as at the method level! + * If specified at both levels, the method level produces condition overrides + * the type level condition. + * @see org.springframework.http.MediaType + */ + String[] produces() default {}; + + } + """)); } } } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java index 06dae3513..c0d640c64 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttribute.java @@ -52,9 +52,8 @@ public class CopyAnnotationAttribute extends Recipe { example = "timeout") String targetAttributeName; - @Override protected TreeVisitor getSingleSourceApplicableTest() { - return new UsesType<>(sourceAnnotationType); + return new UsesType<>(sourceAnnotationType, true); } @Override @@ -68,7 +67,8 @@ protected TreeVisitor getSingleSourceApplicableTest() { } @Override - protected @NotNull JavaIsoVisitor getVisitor() { + @NotNull + public JavaIsoVisitor getVisitor() { return new CopyAnnotationAttributeVisitor( sourceAnnotationType, sourceAttributeName, diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java index 0ec2b9586..9db9f3e5f 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompanied.java @@ -51,9 +51,9 @@ public class RemoveAnnotationIfAccompanied extends Recipe { return "Remove matching annotation if the other annotation is also present."; } - @Override protected TreeVisitor getSingleSourceApplicableTest() { - return new UsesType<>(annotationTypeToRemove); + // FIXME: (jaxrs) Test in visitor + return new UsesType<>(annotationTypeToRemove, true); } @Override diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.java index 410ee56d1..388706e56 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.java @@ -15,6 +15,7 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; import org.openrewrite.java.ChangeType; import org.openrewrite.java.JavaParser; @@ -27,6 +28,7 @@ import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; import org.springframework.sbm.java.migration.recipes.openrewrite.ReplaceConstantWithAnotherConstant; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,7 +38,10 @@ public class ReplaceMediaType extends Recipe { - public ReplaceMediaType(Supplier javaParserSupplier) { + @Override + public List getRecipeList() { + + List recipeList = new ArrayList<>(); // Constants Map mappings = new HashMap<>(); @@ -83,15 +88,15 @@ public ReplaceMediaType(Supplier javaParserSupplier) { mappings.put("WILDCARD_TYPE", "ALL"); mappings.forEach( - (key, value) -> doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType." + key,"org.springframework.http.MediaType." + value)) + (key, value) -> recipeList.add(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType." + key,"org.springframework.http.MediaType." + value)) ); - doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType.CHARSET_PARAMETER","org.springframework.util.MimeType.PARAM_CHARSET")); - doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType.MEDIA_TYPE_WILDCARD","org.springframework.util.MimeType.WILDCARD_TYPE")); + recipeList.add(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType.CHARSET_PARAMETER","org.springframework.util.MimeType.PARAM_CHARSET")); + recipeList.add(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType.MEDIA_TYPE_WILDCARD","org.springframework.util.MimeType.WILDCARD_TYPE")); // instance methods // #isCompatible(MediaType) - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.MediaType isCompatible(javax.ws.rs.core.MediaType)"), (v, m, addImport) -> { + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.MediaType isCompatible(javax.ws.rs.core.MediaType)"), (v, m, addImport) -> { JavaType type = JavaType.buildType("org.springframework.http.MediaType"); J.Identifier newMethodName = m.getName().withSimpleName("isCompatibleWith"); @@ -107,14 +112,14 @@ public ReplaceMediaType(Supplier javaParserSupplier) { })); // #withCharset(String) - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.MediaType withCharset(java.lang.String)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new MediaType(#{any(org.springframework.http.MediaType)}, Charset.forName(#{any(java.lang.String)}))") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.MediaType withCharset(java.lang.String)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("new MediaType(#{any(org.springframework.http.MediaType)}, Charset.forName(#{any(java.lang.String)}))") .imports("org.springframework.http.MediaType", "java.nio.charset.Charset") .build(); addImport.accept("java.nio.charset.Charset"); addImport.accept("org.springframework.http.MediaType"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); })); // #getParameters() - comes with org.springframework.util.MimeType#getParameters() @@ -130,42 +135,44 @@ public ReplaceMediaType(Supplier javaParserSupplier) { // constructors // MediaType() -> new MediaType(MimeType.WILDCARD_TYPE, MimeType.WILDCARD_TYPE) - doNext(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new MediaType(MimeType.WILDCARD_TYPE, MimeType.WILDCARD_TYPE)") + recipeList.add(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("new MediaType(MimeType.WILDCARD_TYPE, MimeType.WILDCARD_TYPE)") .imports("org.springframework.http.MediaType", "org.springframework.util.MimeType") .build(); addImport.accept("org.springframework.util.MimeType"); addImport.accept("org.springframework.http.MediaType"); - return m.withTemplate(template, m.getCoordinates().replace()); + return template.apply(v.getCursor(), m.getCoordinates().replace()); })); // MediaType(String, String) - present on Spring MediaType - doNext(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String"), (v, m, addImport) -> { + recipeList.add(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String"), (v, m, addImport) -> { JavaType type = JavaType.buildType("org.springframework.http.MediaType"); return m.withConstructorType(m.getConstructorType().withDeclaringType(TypeUtils.asFullyQualified(type))); })); // MediaType(String, String, String) -> MediaType(String, String, Charset) - doNext(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String", "java.lang.String"), (v, m, addImport) -> { + recipeList.add(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String", "java.lang.String"), (v, m, addImport) -> { List arguments = m.getArguments(); - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new MediaType(#{any(java.lang.String)}, #{any(java.lang.String)}, Charset.forName(#{any(java.lang.String)}))") + JavaTemplate template = JavaTemplate.builder("new MediaType(#{any(java.lang.String)}, #{any(java.lang.String)}, Charset.forName(#{any(java.lang.String)}))") .imports("org.springframework.http.MediaType", "java.nio.charset.Charset") .build(); addImport.accept("java.nio.charset.Charset"); addImport.accept("org.springframework.http.MediaType"); - return m.withTemplate(template, m.getCoordinates().replace(), arguments.get(0), arguments.get(1), arguments.get(2)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), arguments.get(0), arguments.get(1), arguments.get(2)); })); // MediaType(String, String, Map) - present on Spring MediaType - doNext(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String", "java.util.Map"), (v, m, addImport) -> { + recipeList.add(new RewriteConstructorInvocation(constructorMatcher("javax.ws.rs.core.MediaType", "java.lang.String", "java.lang.String", "java.util.Map"), (v, m, addImport) -> { JavaType type = JavaType.buildType("org.springframework.http.MediaType"); return m.withConstructorType(m.getConstructorType().withDeclaringType(TypeUtils.asFullyQualified(type))); })); // Type references - doNext(new ChangeType("javax.ws.rs.core.MediaType", "org.springframework.http.MediaType", false)); + recipeList.add(new ChangeType("javax.ws.rs.core.MediaType", "org.springframework.http.MediaType", false)); + + return recipeList; } @Override @@ -173,4 +180,9 @@ public String getDisplayName() { return "Replace JAX-RS MediaType with Spring MediaType"; } + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java index 4441adb67..0f0ffa5af 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.java @@ -16,23 +16,37 @@ package org.springframework.sbm.jee.jaxrs.recipes; import org.jetbrains.annotations.NotNull; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; +import org.openrewrite.config.RecipeExample; + +import java.util.ArrayList; +import java.util.List; /** * @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, ..."; } + + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + + @Override + public List getRecipeList() { + List recipeList = new ArrayList<>(); + recipeList.add(new CopyAnnotationAttribute( + "javax.ws.rs.DefaultValue", "value", "org.springframework.web.bind.annotation.RequestParam", "defaultValue") + ); + recipeList.add(new RemoveAnnotationIfAccompanied( + "javax.ws.rs.DefaultValue", "org.springframework.web.bind.annotation.RequestParam" + )); + return recipeList; + } } \ No newline at end of file diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java index 1a04aec6a..86b993db3 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java @@ -15,6 +15,7 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; import org.openrewrite.Tree; import org.openrewrite.java.ChangeType; @@ -36,35 +37,36 @@ public class ReplaceResponseEntityBuilder extends Recipe { - public ReplaceResponseEntityBuilder() { + @Override + public List getRecipeList() { + List recipeList = new ArrayList<>(); + // #allow(String...) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder allow(java.lang.String...)"), (v, m, addImport) -> { String transformedArgs = m.getArguments().stream().map(arg -> "HttpMethod.resolve(#{any()})").collect(Collectors.joining(", ")); - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.allow(" + transformedArgs + ")").imports("org.springframework.http.HttpMethod", "org.springframework.http.ResponseEntity.HeadersBuilder").build(); + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.allow(" + transformedArgs + ")").imports("org.springframework.http.HttpMethod", "org.springframework.http.ResponseEntity.HeadersBuilder").build(); // v.maybeAddImport("org.springframework.http.HttpMethod"); addImport.accept("org.springframework.http.HttpMethod"); List parameters = new ArrayList(); parameters.add(m.getSelect()); parameters.addAll(m.getArguments()); - return m.withTemplate(t, m.getCoordinates().replace(), parameters.toArray()); + return t.apply(v.getCursor(), m.getCoordinates().replace(), parameters.toArray()); } ) ); // #allow(Set) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder allow(java.util.Set)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder( - () -> v.getCursor(), - "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.allow(#{any()}.stream().map(HttpMethod::resolve).toArray(String[]::new))").imports("org.springframework.http.HttpMethod", "org.springframework.http.ResponseEntity.HeadersBuilder" - ) + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.allow(#{any()}.stream().map(HttpMethod::resolve).toArray(String[]::new))") + .imports("org.springframework.http.HttpMethod", "org.springframework.http.ResponseEntity.HeadersBuilder") .build(); // v.maybeAddImport("org.springframework.http.HttpMethod"); addImport.accept("org.springframework.http.HttpMethod"); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); @@ -72,80 +74,77 @@ public ReplaceResponseEntityBuilder() { // #cacheControl(CacheControl) // #encoding(String) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder encoding(java.lang.String)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder( - () -> v.getCursor(), - "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.header(HttpHeaders.CONTENT_ENCODING, #{any()})" - ) + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.header(HttpHeaders.CONTENT_ENCODING, #{any()})") .imports("org.springframework.http.HttpHeaders", "org.springframework.http.ResponseEntity.HeadersBuilder") .build(); addImport.accept("org.springframework.http.HttpHeaders"); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments()); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments()); } ) ); // #contentLocation(URI) - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder contentLocation(java.net.URI)"), "location", "org.springframework.http.ResponseEntity.HeadersBuilder")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder contentLocation(java.net.URI)"), "location", "org.springframework.http.ResponseEntity.HeadersBuilder")); // #tag(String) - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder tag(java.lang.String)"), "eTag", "org.springframework.http.ResponseEntity.HeadersBuilder")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder tag(java.lang.String)"), "eTag", "org.springframework.http.ResponseEntity.HeadersBuilder")); // #entity(Object) // #entity(Object, Annotation[]) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder entity(java.lang.Object, ..)"), (v, m, addImport) -> { VisitorUtils.markWrappingInvocationWithTemplate(v, m, new MethodMatcher("javax.ws.rs.core.Response.ResponseBuilder build()"), m.getArguments().get(0).print(), this); - return m.withTemplate(JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}").build(), m.getCoordinates().replace(), m.getSelect()); + return JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}").build().apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); } ) ); // #expires(Date) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder expires(java.util.Date)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.setExpires(#{any()}.toInstant()))") + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.setExpires(#{any()}.toInstant()))") .build(); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); // #language(String) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder language(java.lang.String)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.set(HttpHeaders.CONTENT_LANGUAGE, #{any()}))") + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.set(HttpHeaders.CONTENT_LANGUAGE, #{any()}))") .imports("org.springframework.http.HttpHeaders") .build(); addImport.accept("org.springframework.http.HttpHeaders"); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); // #language(Locale) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder language(java.util.Locale)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.setContentLanguage(#{any()}))") + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.setContentLanguage(#{any()}))") .build(); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); // #lastModified(Date) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder lastModified(java.util.Date)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.lastModified(#{any()}.toInstant())") + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.lastModified(#{any()}.toInstant())") .build(); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); @@ -153,45 +152,45 @@ public ReplaceResponseEntityBuilder() { // #location(URI) - present on Spring ResponseEntity builder classes, nothing to do // replaceAll(MultivaluedMap) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder replaceAll(javax.ws.rs.core.MultivaluedMap)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> {\n" + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> {\n" + "h.clear();\n" + "h.addAll(#{any()});\n" + "})") .imports("org.springframework.util.MultiValueMap", "org.springframework.http.ResponseEntity.HeadersBuilder") .build(); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); // #type(String) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder type(java.lang.String)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.set(HttpHeaders.CONTENT_TYPE, #{any()}))") + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.set(HttpHeaders.CONTENT_TYPE, #{any()}))") .imports("org.springframework.http.HttpHeaders", "org.springframework.http.ResponseEntity.HeadersBuilder") .build(); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); // #type(MediaType) - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder type(javax.ws.rs.core.MediaType)"), "contentType", "org.springframework.http.ResponseEntity.HeadersBuilder")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder type(javax.ws.rs.core.MediaType)"), "contentType", "org.springframework.http.ResponseEntity.HeadersBuilder")); // #build() // FIXME: org.springframework.http.ResponseEntity.build() does not exist. Invalid: ResponseEntity r = ResponseEntity.ok("...").build(); - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder build()"), (v, m, addImport) -> { MarkWithTemplate marker = m.getMarkers().findFirst(MarkWithTemplate.class).orElse(null); if (marker != null) { m = VisitorUtils.removeMarker(m, marker); - Builder t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.body(#{})"); - m = m.withTemplate(t.build(), m.getCoordinates().replace(), m.getSelect(), marker.getTemplate()); + Builder t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.body(#{})"); + m = t.build().apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), marker.getTemplate()); } return m.withMarkers(m.getMarkers().computeByType(new MarkReturnType(Tree.randomId(), this, "ResponseEntity", "org.springframework.http.ResponseEntity"), (o1, o2) -> o2)); } @@ -217,27 +216,28 @@ public ReplaceResponseEntityBuilder() { // Always should be the last for method call migration // Take care of #body(Object) calls that were marked and should go at the end of method invocation chain - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( m -> m.getMarkers().findFirst(MarkWithTemplate.class).isPresent(), (v, m, addImport) -> { MarkWithTemplate marker = m.getMarkers().findFirst(MarkWithTemplate.class).orElse(null); m = VisitorUtils.removeMarker(m, marker); - return m - .withTemplate( - JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.body(#{}").build(), - m.getCoordinates().replace(), - m, - marker.getTemplate()) - .withMarkers(m.getMarkers().computeByType(new MarkReturnType(Tree.randomId(), this, "ResponseEntity", "org.springframework.http.ResponseEntity"), (o1, o2) -> o2)); + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.body(#{}").build(); + return + t.apply(v.getCursor(), + m.getCoordinates().replace(), + m, + marker.getTemplate()) + .withMarkers(m.getMarkers().computeByType(new MarkReturnType(Tree.randomId(), this, "ResponseEntity", "org.springframework.http.ResponseEntity"), (o1, o2) -> o2)); } ) ); - doNext(new AdjustTypesFromExpressionMarkers()); + recipeList.add(new AdjustTypesFromExpressionMarkers()); // Finally replace type with BodyBuilder if nothing else replaced it previously - doNext(new ChangeType("javax.ws.rs.core.Response$ResponseBuilder", "org.springframework.http.ResponseEntity$BodyBuilder", true)); + recipeList.add(new ChangeType("javax.ws.rs.core.Response$ResponseBuilder", "org.springframework.http.ResponseEntity$BodyBuilder", true)); + return recipeList; } @Override @@ -245,4 +245,9 @@ public String getDisplayName() { return "Replace references to JAX-RS ReplaceResponseEntityBuilder"; } + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.java index e4eb8116e..e4c0a77b9 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.java @@ -15,15 +15,22 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; import org.openrewrite.java.ChangeType; import org.openrewrite.java.JavaTemplate; import org.springframework.sbm.java.migration.recipes.RewriteConstructorInvocation; import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; +import java.util.ArrayList; +import java.util.List; + public class SwapCacheControl extends Recipe { - public SwapCacheControl() { + @Override + public List getRecipeList() { + + List recipeList = new ArrayList<>(); /* * NOT SUPPORTED: @@ -49,25 +56,27 @@ public SwapCacheControl() { */ // setSMaxAge(int) - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.CacheControl setSMaxAge(int)"), (v, m, addImport) -> { - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.CacheControl)}.sMaxAge(#{any(int)}, TimeUnit.SECONDS)") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.CacheControl setSMaxAge(int)"), (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.CacheControl)}.sMaxAge(#{any(int)}, TimeUnit.SECONDS)") .imports("java.util.concurrent.TimeUnit", "org.springframework.http.CacheControl") .build(); addImport.accept("java.util.concurrent.TimeUnit"); - return m.withTemplate(t, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); })); // constructor - doNext(new RewriteConstructorInvocation(RewriteConstructorInvocation.constructorMatcher("javax.ws.rs.core.CacheControl"), (v, c, addImport) -> { - JavaTemplate t = JavaTemplate.builder(() -> v.getCursor(), "CacheControl.empty()") + recipeList.add(new RewriteConstructorInvocation(RewriteConstructorInvocation.constructorMatcher("javax.ws.rs.core.CacheControl"), (v, c, addImport) -> { + JavaTemplate t = JavaTemplate.builder("CacheControl.empty()") .imports("org.springframework.http.CacheControl") .build(); addImport.accept("org.springframework.http.CacheControl"); v.maybeRemoveImport("javax.ws.rs.core.CacheControl"); - return c.withTemplate(t, c.getCoordinates().replace()); + return t.apply(v.getCursor(), c.getCoordinates().replace()); })); - doNext(new ChangeType("javax.ws.rs.core.CacheControl", "org.springframework.http.CacheControl", false)); + recipeList.add(new ChangeType("javax.ws.rs.core.CacheControl", "org.springframework.http.CacheControl", false)); + + return recipeList; } @Override @@ -75,4 +84,9 @@ public String getDisplayName() { return "Swap JAX-RS CacheControl with Spring CacheControl"; } + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java index fab2b8309..edaf215b6 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java @@ -15,18 +15,26 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; import org.openrewrite.java.ChangeType; import org.openrewrite.java.JavaTemplate; +import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; import org.springframework.sbm.java.migration.recipes.openrewrite.ReplaceConstantWithAnotherConstant; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class SwapFamilyForSeries extends Recipe { - public SwapFamilyForSeries() { + @Override + public List getRecipeList() { + + List recipeList = new ArrayList<>(); + Map fieldsMapping = new HashMap<>(); fieldsMapping.put("INFORMATIONAL", "INFORMATIONAL"); fieldsMapping.put("SUCCESSFUL", "SUCCESSFUL"); @@ -34,23 +42,23 @@ public SwapFamilyForSeries() { fieldsMapping.put("CLIENT_ERROR", "CLIENT_ERROR"); fieldsMapping.put("SERVER_ERROR", "SERVER_ERROR"); fieldsMapping.forEach( - (key, value) -> doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.Response.Status.Family." + key,"org.springframework.http.HttpStatus.Series." + value)) + (key, value) -> recipeList.add(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.Response.Status.Family." + key,"org.springframework.http.HttpStatus.Series." + value)) ); // All constants seem to match on both types - let ChangeType take care of type changing for field accesses - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.Status.Family familyOf(int)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "HttpStatus.Series.resolve(#{any(int)})").build(); + JavaTemplate template = JavaTemplate.builder("HttpStatus.Series.resolve(#{any(int)})").build(); v.maybeAddImport("org.springframework.http.HttpStatus.Series"); addImport.accept("org.springframework.http.HttpStatus"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); } ) ); - doNext(new ChangeType("javax.ws.rs.core.Response$Status$Family", "org.springframework.http.HttpStatus$Series", true)); - + recipeList.add(new ChangeType("javax.ws.rs.core.Response$Status$Family", "org.springframework.http.HttpStatus$Series", true)); + return recipeList; } @Override @@ -58,4 +66,9 @@ public String getDisplayName() { return "Swap JAX-RS Family with Spring HttpStatus.Series"; } + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java index 9e0f2b94b..742d28389 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java @@ -15,6 +15,7 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; import org.openrewrite.java.ChangeType; import org.openrewrite.java.JavaTemplate; @@ -24,6 +25,7 @@ import org.openrewrite.java.tree.TypeUtils; import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; +import java.util.ArrayList; import java.util.List; import static org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation.methodInvocationMatcher; @@ -31,7 +33,19 @@ public class SwapHttHeaders extends Recipe { - public SwapHttHeaders() { + + @Override + public String getDisplayName() { + return "Swap JAX-RS HttpHeaders with Spring HttpHeaders"; + } + + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + + @Override + public List getRecipeList() { /* * NOT SUPPORTED: * - CONTENT_ID (possibly replace with "Content-ID") @@ -39,8 +53,11 @@ public SwapHttHeaders() { * - getAcceptableMediaTypes() * - getCookies() */ + + List recipeList = new ArrayList<>(); + // #getAcceptableLanguages() - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getAcceptableLanguages()"), (v, m, addImport) -> { JavaType javaType = JavaType.buildType("org.springframework.http.HttpHeaders"); @@ -53,14 +70,14 @@ public SwapHttHeaders() { ); // #getDate() - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getDate()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new Date()") + JavaTemplate template = JavaTemplate.builder("new Date()") .imports("java.util.Date") .build(); addImport.accept("java.util.Date"); - NewClass newMethod = (NewClass) m.withTemplate(template, m.getCoordinates().replace()); + NewClass newMethod = (NewClass) template.apply(v.getCursor(), m.getCoordinates().replace()); JavaType javaType = JavaType.buildType("org.springframework.http.HttpHeaders"); return newMethod.withArguments(List.of( m.withSelect(m.getSelect().withType(javaType)) @@ -72,18 +89,18 @@ public SwapHttHeaders() { ); // #getHeaderString(String) - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getHeaderString(java.lang.String)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "String.join(\", \", #{any(org.springframework.http.HttpHeaders)}.get(#{any(java.lang.String)})") + JavaTemplate template = JavaTemplate.builder("String.join(\", \", #{any(org.springframework.http.HttpHeaders)}.get(#{any(java.lang.String)})") .build(); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); // #getLanguage() - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getLanguage()"), (v, m, addImport) -> { JavaType javaType = JavaType.buildType("org.springframework.http.HttpHeaders"); @@ -96,7 +113,7 @@ public SwapHttHeaders() { ); // #getLength() - doNext(new RewriteMethodInvocation( + recipeList.add(new RewriteMethodInvocation( methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getLength()"), (v, m, addImport) -> { JavaType javaType = JavaType.buildType("org.springframework.http.HttpHeaders"); @@ -109,33 +126,29 @@ public SwapHttHeaders() { ); // #getMediaType() - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getMediaType()"), "getContentType", "org.springframework.http.HttpHeaders")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getMediaType()"), "getContentType", "org.springframework.http.HttpHeaders")); // #getRequestHeader(String) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getRequestHeader(java.lang.String)"), + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getRequestHeader(java.lang.String)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.HttpHeaders)}.get(#{any(java.lang.String)})") + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.HttpHeaders)}.get(#{any(java.lang.String)})") .build(); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); } ) ); // #getRequestHeaders() - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getRequestHeaders()"), + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getRequestHeaders()"), (v, m, addImport) -> { // Spring HttpHeaders is a MultiValueMap, hence just leave the expression and remove the call - return m.withTemplate(JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.HttpHeaders)}").build(), m.getCoordinates().replace(), m.getSelect()); + return JavaTemplate.builder("#{any(org.springframework.http.HttpHeaders)}").build().apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); } ) ); - doNext(new ChangeType("javax.ws.rs.core.HttpHeaders", "org.springframework.http.HttpHeaders", false)); - } + recipeList.add(new ChangeType("javax.ws.rs.core.HttpHeaders", "org.springframework.http.HttpHeaders", false)); - @Override - public String getDisplayName() { - return "Swap JAX-RS HttpHeaders with Spring HttpHeaders"; + return recipeList; } - } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java index 33ade0d0b..54ee2a28b 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java @@ -16,6 +16,7 @@ package org.springframework.sbm.jee.jaxrs.recipes; import org.openrewrite.ExecutionContext; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; import org.openrewrite.Tree; import org.openrewrite.java.*; @@ -26,6 +27,7 @@ import org.springframework.sbm.java.migration.visitor.VisitorUtils; import org.springframework.sbm.search.recipe.CommentJavaSearchResult; +import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -35,317 +37,318 @@ public class SwapResponseWithResponseEntity extends Recipe { - public SwapResponseWithResponseEntity(Supplier javaParserSupplier) { + public SwapResponseWithResponseEntity() { + List recipeList = new ArrayList<>(); - doNext(new SwapStatusForHttpStatus(javaParserSupplier)); + recipeList.add(new SwapStatusForHttpStatus()); // #status(int) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(int)"), (v, m, addImport) -> { + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(int)"), (v, m, addImport) -> { String args = m.getArguments().stream().map(a -> "#{any()}").collect(Collectors.joining(", ")); - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(" + args + ")") + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(" + args + ")") .imports("org.springframework.http.ResponseEntity") .build(); v.maybeRemoveImport("javax.ws.rs.core.Response"); addImport.accept("org.springframework.http.ResponseEntity"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().toArray()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().toArray()); })); // #status(int, String) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(int, java.lang.String)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(#{any()})") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(int, java.lang.String)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(#{any()})") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(int, java.lang.String) - dropped java.lang.String argument"))); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(int, java.lang.String) - dropped java.lang.String argument"))); })); // #status(Status) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(javax.ws.rs.core.Response.Status)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(#{any()})") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(javax.ws.rs.core.Response.Status)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(#{any()})") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(javax.ws.rs.core.Response.StatusType) - replaced with status(int)"))); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(javax.ws.rs.core.Response.StatusType) - replaced with status(int)"))); })); // #status(StatusType) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(javax.ws.rs.core.Response.StatusType)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(#{()})") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response status(javax.ws.rs.core.Response.StatusType)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(#{()})") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(javax.ws.rs.core.Response.StatusType) - replaced with status(int)"))); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)).withMarkers(m.getMarkers().add(new CommentJavaSearchResult(Tree.randomId(), "SBM FIXME: Couldn't find exact replacement for status(javax.ws.rs.core.Response.StatusType) - replaced with status(int)"))); })); // #ok() - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.ok()") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.ok()") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace()); + return template.apply(v.getCursor(), m.getCoordinates().replace()); })); // #ok(Object) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object)"), (v, m, addImport) -> { + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object)"), (v, m, addImport) -> { List args = m.getArguments(); if(J.Literal.class.isInstance(m.getArguments().get(0))) { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.ok()") + JavaTemplate template = JavaTemplate.builder("ResponseEntity.ok()") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - m = m.withTemplate(template, m.getCoordinates().replace()); + m = template.apply(v.getCursor(), m.getCoordinates().replace()); markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); } return m; })); // #ok(Object, MediaType) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object, javax.ws.rs.core.MediaType)"), (v, m, addImport) -> { + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object, javax.ws.rs.core.MediaType)"), (v, m, addImport) -> { List args = m.getArguments(); - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.ok().contentType(#{any()})") + JavaTemplate template = JavaTemplate.builder("ResponseEntity.ok().contentType(#{any()})") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); - return m.withTemplate(template, m.getCoordinates().replace(), args.get(1)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), args.get(1)); })); // #ok(Object, String) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object, java.lang.String)"), (v, m, addImport) -> { + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response ok(java.lang.Object, java.lang.String)"), (v, m, addImport) -> { List args = m.getArguments(); - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.ok().contentType(MediaType.parseMediaType(#{any()}))") + JavaTemplate template = JavaTemplate.builder("ResponseEntity.ok().contentType(MediaType.parseMediaType(#{any()}))") .imports("org.springframework.http.ResponseEntity", "org.springframework.http.MediaType") .build(); addImport.accept("org.springframework.http.ResponseEntity"); addImport.accept("org.springframework.http.MediaType"); v.maybeRemoveImport("javax.ws.rs.core.Response"); markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); - return m.withTemplate(template, m.getCoordinates().replace(), args.get(1)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), args.get(1)); })); // #accepted() - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response accepted()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.accepted()") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response accepted()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.accepted()") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace()); + return template.apply(v.getCursor(), m.getCoordinates().replace()); })); // #accepted(Object) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response accepted(java.lang.Object)"), (v, m, addImport) -> { + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response accepted(java.lang.Object)"), (v, m, addImport) -> { List args = m.getArguments(); - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.accepted()") + JavaTemplate template = JavaTemplate.builder("ResponseEntity.accepted()") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - m = m.withTemplate(template, m.getCoordinates().replace()); + m = template.apply(v.getCursor(), m.getCoordinates().replace()); markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); return m; })); // #created(URI) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response created(java.net.URI)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.created(#{any()})") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response created(java.net.URI)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.created(#{any()})") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); })); // #fromResponse(Response) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response fromResponse(javax.ws.rs.core.Response)"), (v, m, addImport) -> { + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response fromResponse(javax.ws.rs.core.Response)"), (v, m, addImport) -> { Expression e = m.getArguments().get(0); - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(#{any()}.getStatusCode()).headers(#{any()}.getHeaders())") + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(#{any()}.getStatusCode()).headers(#{any()}.getHeaders())") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); markTopLevelInvocationWithTemplate(v, m, e.print() + ".getBody()"); - return m.withTemplate(template, m.getCoordinates().replace(), e, e); + return template.apply(v.getCursor(), m.getCoordinates().replace(), e, e); })); // #noContent() // TODO: returns HeadersBuilder - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response noContent()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.noContent()") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response noContent()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.noContent()") .imports("org.springframework.http.ResponseEntity") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace()); + return template.apply(v.getCursor(), m.getCoordinates().replace()); })); // #notAcceptable(List { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.NOT_MODIFIED)") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response notModified()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(HttpStatus.NOT_MODIFIED)") .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") .build(); addImport.accept("org.springframework.http.ResponseEntity"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace()); + return template.apply(v.getCursor(), m.getCoordinates().replace()); })); // notModified(String) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response notModified(java.lang.String)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.NOT_MODIFIED).eTag(#{any()})") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response notModified(java.lang.String)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(HttpStatus.NOT_MODIFIED).eTag(#{any()})") .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") .build(); addImport.accept("org.springframework.http.ResponseEntity"); addImport.accept("org.springframework.http.HttpStatus"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); })); // notModified(EntityTag) - migration not supported // #seeOther(URI) // TODO: Returns BodyBuilder - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response seeOther(java.net.URI)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.SEE_OTHER).location(#{any()})") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response seeOther(java.net.URI)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(HttpStatus.SEE_OTHER).location(#{any()})") .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") .build(); addImport.accept("org.springframework.http.ResponseEntity"); addImport.accept("org.springframework.http.HttpStatus"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); })); // #serverError() // Returns BodyBuilder - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response serverError()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.SERVER_ERROR)") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response serverError()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(HttpStatus.SERVER_ERROR)") .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") .build(); addImport.accept("org.springframework.http.ResponseEntity"); addImport.accept("org.springframework.http.HttpStatus"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace()); + return template.apply(v.getCursor(), m.getCoordinates().replace()); })); // #temporaryRedirect(URI) // TODO: Returns BodyBuilder - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response temporaryRedirect(java.net.URI)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(#{any()})") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response temporaryRedirect(java.net.URI)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(#{any()})") .imports("org.springframework.http.ResponseEntity", "org.springframework.http.HttpStatus") .build(); addImport.accept("org.springframework.http.ResponseEntity"); addImport.accept("org.springframework.http.HttpStatus"); v.maybeRemoveImport("javax.ws.rs.core.Response"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); })); // INSTANCE METHODS // #getAllowedMethods() - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getAllowedMethods()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getAllow().stream().map(m -> m.toString()).collect(Collectors.toList())") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getAllowedMethods()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity)}.getHeaders().getAllow().stream().map(m -> m.toString()).collect(Collectors.toList())") .imports("java.util.stream.Collectors", "org.springframework.http.ResponseEntity") .build(); addImport.accept("java.util.stream.Collectors"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getDate() - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getDate()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new Date(#{any(org.springframework.http.ResponseEntity)}.getHeaders().getDate())") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getDate()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("new Date(#{any(org.springframework.http.ResponseEntity)}.getHeaders().getDate())") .imports("java.util.Date", "org.springframework.http.ResponseEntity") .build(); addImport.accept("java.util.Date"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getEntity() - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getEntity()"), "getBody", "org.springframework.http.ResponseEntity")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getEntity()"), "getBody", "org.springframework.http.ResponseEntity")); // #getEntityTag() // TODO: return type not EntityTag but String after migration - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getEntityTag()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getETag()") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getEntityTag()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity)}.getHeaders().getETag()") .build(); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getHeaderString(String) - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getHeaderString(java.lang.String)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().get(#{any()}).stream().collect(Collectors.joining(\", \"))") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getHeaderString(java.lang.String)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity)}.getHeaders().get(#{any()}).stream().collect(Collectors.joining(\", \"))") .imports("java.util.stream.Collectors") .build(); v.maybeAddImport("java.util.stream.Collectors"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); })); // #getMetadata() - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getMetadata()"), "getHeaders", "org.springframework.http.ResponseEntity")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getMetadata()"), "getHeaders", "org.springframework.http.ResponseEntity")); // #getLanguage() - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLanguage()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentLanguage()") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLanguage()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentLanguage()") .build(); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getLastModified() - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLastModified()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "new Date(#{any(org.springframework.http.ResponseEntity)}.getHeaders().getLastModified())") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLastModified()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("new Date(#{any(org.springframework.http.ResponseEntity)}.getHeaders().getLastModified())") .imports("java.util.Date") .build(); addImport.accept("java.util.Date"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getLength() - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLength()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentLength()") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLength()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentLength()") .build(); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getLocation() - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLocation()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getLocation()") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getLocation()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity)}.getHeaders().getLocation()") .build(); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getMediaType() - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getMediaType()"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentType()") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response getMediaType()"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity)}.getHeaders().getContentType()") .build(); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getStatus() - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStatus()"), "getStatusCodeValue", "org.springframework.http.ResponseEntity")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStatus()"), "getStatusCodeValue", "org.springframework.http.ResponseEntity")); // #getStatusInfo() - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStatusInfo()"), "getStatusCode", "org.springframework.http.ResponseEntity")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStatusInfo()"), "getStatusCode", "org.springframework.http.ResponseEntity")); // #getStringHeaders() // TODO: different return type - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStringHeaders()"), "getHeaders", "org.springframework.http.ResponseEntity")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStringHeaders()"), "getHeaders", "org.springframework.http.ResponseEntity")); // #hasEntity() - doNext(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response hasEntity()"), "hasBody", "org.springframework.http.ResponseEntity")); + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response hasEntity()"), "hasBody", "org.springframework.http.ResponseEntity")); // #readEntity(..) - doNext(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response readEntity(..)"), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.ResponseEntity)}.getBody())") + recipeList.add(new RewriteMethodInvocation(RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response readEntity(..)"), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity)}.getBody())") .build(); v.maybeRemoveImport("java.lang.annotation.Annotation"); v.maybeRemoveImport("javax.ws.rs.core.GenericType"); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // #getHeaders() - present on ResponseEntity but different return type. Nothing can be done about it for now... @@ -359,9 +362,9 @@ public SwapResponseWithResponseEntity(Supplier javaParserSupplier) { // #hasLink() - not implemented - doNext(new ReplaceResponseEntityBuilder()); + recipeList.add(new ReplaceResponseEntityBuilder()); - doNext(new ChangeType("javax.ws.rs.core.Response", "org.springframework.http.ResponseEntity", false)); + recipeList.add(new ChangeType("javax.ws.rs.core.Response", "org.springframework.http.ResponseEntity", false)); } private void markTopLevelInvocationWithTemplate(JavaVisitor v, MethodInvocation m, String template) { @@ -373,4 +376,9 @@ public String getDisplayName() { return "Replace JAX-RS Response with Spring ResponseEntity"; } + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java index 3de007445..66b18784e 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java @@ -15,6 +15,7 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; +import org.openrewrite.NlsRewrite; import org.openrewrite.Recipe; import org.openrewrite.java.ChangeType; import org.openrewrite.java.JavaParser; @@ -22,7 +23,9 @@ import org.springframework.sbm.java.migration.recipes.RewriteMethodInvocation; import org.springframework.sbm.java.migration.recipes.openrewrite.ReplaceConstantWithAnotherConstant; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.function.Supplier; @@ -30,9 +33,13 @@ public class SwapStatusForHttpStatus extends Recipe { - public SwapStatusForHttpStatus(Supplier javaParserSupplier) { + @Override + public List getRecipeList() { + + List recipeList = new ArrayList<>(); + // Switch JAX-RS Family to Spring HttpStatus.Series - doNext(new SwapFamilyForSeries()); + recipeList.add(new SwapFamilyForSeries()); Map fieldsMapping = new HashMap<>(); fieldsMapping.put("OK", "OK"); @@ -80,24 +87,24 @@ public SwapStatusForHttpStatus(Supplier javaParserSupplier) { fieldsMapping.put("USE_PROXY", "USE_PROXY"); fieldsMapping.forEach( - (key, value) -> doNext(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.Response$Status." + key,"org.springframework.http.HttpStatus." + value)) + (key, value) -> recipeList.add(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.Response$Status." + key,"org.springframework.http.HttpStatus." + value)) ); // Instance methods - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType getStatusCode()") + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType getStatusCode()") .or(methodInvocationMatcher("javax.ws.rs.core.Response.Status getStatusCode()")), (v, m, addImport) -> { return m.withName(m.getName().withSimpleName("getValue")); })); // Remove #toEnum() method calls - these shouldn't appear as we migrate both Jax-Rs Status and StatusType to the same HttpStatus - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType toEnum()").or(methodInvocationMatcher("javax.ws.rs.core.Response.Status toEnum()")), (v, m, addImport) -> { - JavaTemplate template = JavaTemplate.builder(() -> v.getCursor(), "#{any(org.springframework.http.HttpStatus)}").build(); - return m.withTemplate(template, m.getCoordinates().replace(), m.getSelect()); + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType toEnum()").or(methodInvocationMatcher("javax.ws.rs.core.Response.Status toEnum()")), (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.HttpStatus)}").build(); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); })); // Switch Family to Series - doNext(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType getFamily()").or(methodInvocationMatcher("javax.ws.rs.core.Response.Status getFamily()")), (v, m, addImport) -> { + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.StatusType getFamily()").or(methodInvocationMatcher("javax.ws.rs.core.Response.Status getFamily()")), (v, m, addImport) -> { return m.withName(m.getName().withSimpleName("series")); })); @@ -105,8 +112,10 @@ public SwapStatusForHttpStatus(Supplier javaParserSupplier) { // Type reference replacement - doNext(new ChangeType("javax.ws.rs.core.Response$StatusType", "org.springframework.http.HttpStatus", false)); - doNext(new ChangeType("javax.ws.rs.core.Response$Status", "org.springframework.http.HttpStatus", false)); + recipeList.add(new ChangeType("javax.ws.rs.core.Response$StatusType", "org.springframework.http.HttpStatus", false)); + recipeList.add(new ChangeType("javax.ws.rs.core.Response$Status", "org.springframework.http.HttpStatus", false)); + + return recipeList; } @Override @@ -114,4 +123,9 @@ public String getDisplayName() { return "Swap Jax-RS Status with Spring HttpStatus"; } + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java index c171cbacd..90e59b492 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java @@ -62,7 +62,7 @@ public J.Annotation visitAnnotation(@NotNull J.Annotation annotation, @NotNull E 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) + JavaIsoVisitor addOrUpdateAnnotationAttributeVisitor = (JavaIsoVisitor) new AddOrUpdateAnnotationAttribute(targetAnnotationType, targetAttributeName, targetAttributeValue, false) .getVisitor(); if (targetAnnotationOnlyHasOneLiteralArgument(a)) { a = (J.Annotation) addOrUpdateAnnotationAttributeVisitor.visit(a, ctx, getCursor()); diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java index f238063c4..e6d005775 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java @@ -133,7 +133,7 @@ public JavaSource generateEndpoint(Module module) { JavaSource javaSource = generateEndpointSource(module, ops); - javaSource.apply(new OrderImports(false).doNext(new AutoFormat())); + javaSource.apply(new OrderImports(false), new AutoFormat()); return javaSource; } @@ -232,15 +232,16 @@ private JavaSource generateEndpointSource(Module module, List deps = Arrays.stream("/Users/fkrueger/.m2/repository/org/jboss/spec/javax/ws/rs/jboss-jaxrs-api_2.1_spec/1.0.1.Final/jboss-jaxrs-api_2.1_spec-1.0.1.Final.jar".split(":")).map(d -> Path.of(d)).toList(); + + spec.parser(JavaParser.fromJavaVersion().classpath(deps)); + String RECIPE = "example.recipe.SbmAdapterRecipe"; + + spec.recipe(Environment.builder() + .scanRuntimeClasspath("example.recipe") + .build() + .activateRecipes(RECIPE)); + } + + @Test + public void theTest() { + rewriteRun( + (spec) -> spec.expectedCyclesThatMakeChanges(2), + mavenProject("project", + Assertions.java( + """ + package com.example.jee.app; + + import javax.ws.rs.*; + import javax.ws.rs.core.MediaType; + import javax.ws.rs.core.Response; + import java.util.stream.Collectors; + + import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + import static javax.ws.rs.core.Response.Status.Family.SUCCESSFUL; + + @Path("/") + @Produces("application/json") + public class PersonController { + + @POST + @Path("/json/{name}") + @Consumes("application/json") + public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "{\\"Hello\\":\\"" + name + "\\""; + } + + @GET + @Path("/json") + @Produces(APPLICATION_JSON) + @Consumes(APPLICATION_JSON) + public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { + return "{\\"message\\":\\"No person here...\\""; + } + + + @POST + @Path("/xml/{name}") + @Produces(MediaType.APPLICATION_XML) + @Consumes(MediaType.APPLICATION_XML) + public String getHelloWorldXML(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "Hello "+name+""; + } + + private boolean isResponseStatusSuccessful(Response.Status.Family family) { + return family == SUCCESSFUL; + } + + } + """, + """ + package com.example.jee.app; + + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; + + import javax.ws.rs.DefaultValue; + import javax.ws.rs.PathParam; + import javax.ws.rs.QueryParam; + import javax.ws.rs.core.MediaType; + import javax.ws.rs.core.Response; + + import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + import static javax.ws.rs.core.Response.Status.Family.SUCCESSFUL; + + + @RestController + @RequestMapping(value = "/", produces = "application/json") + public class PersonController { + + @RequestMapping(value = "/json/{name}", consumes = "application/json", method = RequestMethod.POST) + public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "{\\"Hello\\":\\"" + name + "\\""; + } + + @RequestMapping(value = "/json", produces = APPLICATION_JSON, consumes = APPLICATION_JSON, method = RequestMethod.GET) + public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { + return "{\\"message\\":\\"No person here...\\""; + } + + + @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) + public String getHelloWorldXML(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "Hello "+name+""; + } + + private boolean isResponseStatusSuccessful(Response.Status.Family family) { + return family == SUCCESSFUL; + } + + } + """ + ), + pomXml( + //language=xml + """ + + + 4.0.0 + org.springframework.sbm.examples + migrate-jax-rs + jar + 0.0.1-SNAPSHOT + + UTF-8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + + + org.jboss.spec.javax.ws.rs + jboss-jaxrs-api_2.1_spec + 1.0.1.Final + + + + + jcenter + jcenter + https://jcenter.bintray.com + + + mavencentral + mavencentral + https://repo.maven.apache.org/maven2 + + + + """ + ) + ) + ); + } + } + + @Test + @DisplayName("jax-rs recipes through adapter in openrewrite") + void jaxRsRecipesThroughAdapterInOpenrewrite(@TempDir Path tmpDir) throws IOException { + + String projectRootDir = "jee/jaxrs/bootify-jaxrs"; + Path from = Path.of("./testcode").toAbsolutePath().normalize().resolve(projectRootDir).resolve("given"); + Path to = Path.of("./target/test-projects/").resolve(projectRootDir).toAbsolutePath().normalize(); + if(Files.exists(to)) { + FileUtils.deleteDirectory(to.toFile()); + } + Files.createDirectories(to); + FileUtils.deleteDirectory(to.toFile()); + FileUtils.forceMkdir(to.toFile()); + FileUtils.copyDirectory(from.toFile(), to.toFile()); + + String mavenPluginVersion = ""; + String gradlePluginVersion = ""; + Path baseDir = tmpDir; + +// mvn -B --fail-at-end org.openrewrite.maven:rewrite-maven-plugin:5.32.1:dryRun \ +// -Drewrite.activeRecipes=example.recipe.SbmAdapterRecipe \  +// -Drewrite.recipeArtifactCoordinates=org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT \ +// -Dmaven.opts=“-Xms256M -Xmx1024M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005“ + + + PluginInvocationResult pluginInvocationResult = RewritePlugin.dryRun() + .mavenPluginVersion(mavenPluginVersion) + .gradlePluginVersion(gradlePluginVersion) + // TODO: Add method to provide path to compiled classes + .recipes("example.recipe.SbmAdapterRecipe") + .dependencies("org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT") +// .withDebugging(5005, true) +// .withDebug() + .onDir(to); + + System.out.println(pluginInvocationResult.capturedOutput()); + + + } +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java index ec5d3bea9..4650e8fe2 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java @@ -35,13 +35,10 @@ class ReplaceMediaTypeTest { private final static String SPRING_VERSION = "5.3.13"; - private final Supplier javaParserSupplier = () -> new RewriteJavaParser(new SpringRewriteProperties(), - new RewriteExecutionContext()); - final private AbstractAction action = new AbstractAction() { @Override public void apply(ProjectContext context) { - ReplaceMediaType r = new ReplaceMediaType(javaParserSupplier); + ReplaceMediaType r = new ReplaceMediaType(); context.getProjectJavaSources().apply(r); } }; @@ -76,7 +73,7 @@ public String getHelloWorldJSON(String name) { ) .build(); - ReplaceMediaType sut = new ReplaceMediaType(javaParserSupplier); + ReplaceMediaType sut = new ReplaceMediaType(); JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); javaSource.apply(sut); @@ -562,7 +559,7 @@ public String getHelloWorldJSON(@PathParam("name") String name) { ) .build(); - ReplaceMediaType r = new ReplaceMediaType(javaParserSupplier); + ReplaceMediaType r = new ReplaceMediaType(); JavaSource javaSource = projectContext.getProjectJavaSources().list().get(0); javaSource.apply(r); diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java index c76c3b9dc..b9f262c6d 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -39,10 +39,10 @@ public class ResponseEntityReplacementTest { new AbstractAction() { @Override public void apply(ProjectContext context) { - Supplier javaParserSupplier = () -> new RewriteJavaParser(new SpringRewriteProperties(), - new RewriteExecutionContext()); - Recipe r = new SwapResponseWithResponseEntity(javaParserSupplier).doNext(new ReplaceMediaType(javaParserSupplier)); - context.getProjectJavaSources().apply(r); + context.getProjectJavaSources().apply( + new SwapResponseWithResponseEntity(), + new ReplaceMediaType() + ); } }; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java index 2e8b7761b..942cecf69 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java @@ -35,8 +35,7 @@ public class ResponseStatusTest { new AbstractAction() { @Override public void apply(ProjectContext context) { - SwapStatusForHttpStatus r = new SwapStatusForHttpStatus(() -> new RewriteJavaParser(new SpringRewriteProperties(), - new RewriteExecutionContext())); + SwapStatusForHttpStatus r = new SwapStatusForHttpStatus(); context.getProjectJavaSources().apply(r); } }; diff --git a/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml b/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml new file mode 100644 index 000000000..5133f242f --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + org.springframework.sbm.examples + migrate-jax-rs + jar + 0.0.1-SNAPSHOT + + UTF-8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + + + org.jboss.spec.javax.ws.rs + jboss-jaxrs-api_2.1_spec + 1.0.1.Final + + + + + jcenter + jcenter + https://jcenter.bintray.com + + + mavencentral + mavencentral + https://repo.maven.apache.org/maven2 + + + diff --git a/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/src/main/java/com/example/jee/app/PersonController.java b/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/src/main/java/com/example/jee/app/PersonController.java new file mode 100644 index 000000000..0a05a3313 --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/src/main/java/com/example/jee/app/PersonController.java @@ -0,0 +1,45 @@ +package com.example.jee.app; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.stream.Collectors; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.Response.Status.Family.SUCCESSFUL; + +@Path("/") +@Produces("application/json") +public class PersonController { + + @POST + @Path("/json/{name}") + @Consumes("application/json") + public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "{\"Hello\":\"" + name + "\""; + } + + @GET + @Path("/json") + @Produces(APPLICATION_JSON) + @Consumes(APPLICATION_JSON) + public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { + return "{\"message\":\"No person here...\""; + } + + + @POST + @Path("/xml/{name}") + @Produces(MediaType.APPLICATION_XML) + @Consumes(MediaType.APPLICATION_XML) + public String getHelloWorldXML(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "Hello "+name+""; + } + + private boolean isResponseStatusSuccessful(Response.Status.Family family) { + return family == SUCCESSFUL; + } + +} diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/api/Method.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/api/Method.java index 5eacf6ca5..ca9d1177e 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/api/Method.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/api/Method.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.regex.Pattern; public interface Method { @@ -35,6 +36,8 @@ public interface Method { void addAnnotation(String snippet, String annotationImport, String... otherImports); + void addAnnotation(String snippet, String annotationImport, Set typeStubs, String... otherImports); + /** * Checks if method is annotated with annotation with {@code annotationFqName}. * diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/api/MethodParam.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/api/MethodParam.java index 98530e2d3..224b3f08e 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/api/MethodParam.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/api/MethodParam.java @@ -16,6 +16,7 @@ package org.springframework.sbm.java.api; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; public interface MethodParam { diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/api/ProjectJavaSources.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/api/ProjectJavaSources.java index 10c6445d4..ecb780faa 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/api/ProjectJavaSources.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/api/ProjectJavaSources.java @@ -25,7 +25,7 @@ public interface ProjectJavaSources { - void apply(Recipe recipe); + void apply(Recipe... recipe); List list(); diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/api/Type.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/api/Type.java index 6c9e4101a..6007c1bd6 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/api/Type.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/api/Type.java @@ -57,6 +57,8 @@ public interface Type { void addAnnotation(String snippet, String annotationImport, String... otherImports); + void addAnnotation(String snippet, String annotationImport, Set typeStubs, String... otherImports); + Annotation getAnnotation(String fqName); void apply(Recipe... r); diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/CompiledType.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/CompiledType.java index 461143446..31d8a1989 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/CompiledType.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/CompiledType.java @@ -117,6 +117,11 @@ public void addAnnotation(String snippet, String annotationImport, String... oth } + @Override + public void addAnnotation(String snippet, String annotationImport, Set typeStubs, String... otherImports) { + + } + @Override public Annotation getAnnotation(String fqName) { return null; diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteMethod.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteMethod.java index 6f05382e5..1a61d133c 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteMethod.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteMethod.java @@ -32,14 +32,10 @@ import org.springframework.sbm.java.api.Visibility; import org.springframework.sbm.java.refactoring.JavaRefactoring; import org.springframework.rewrite.parser.JavaParserBuilder; -import org.springframework.sbm.support.openrewrite.java.AddAnnotationVisitor; import org.springframework.sbm.support.openrewrite.java.RemoveAnnotationVisitor; import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -118,9 +114,42 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex if(md == getMethodDecl()) { J.CompilationUnit cu = getCursor().dropParentUntil(J.CompilationUnit.class::isInstance).getValue(); List dependencies = cu.getMarkers().findFirst(ClasspathDependencies.class).get().getDependencies(); + + // FIXME: (jaxrs): Build JavaParser and use dependsOn to provide stubs for required types JavaParser.Builder clone = javaParserBuilder.classpath(dependencies) .clone(); List imports = Stream.concat(Stream.of(annotationImport), Stream.of(otherImports)).toList(); + + System.out.println("Snippet: " + snippet); + + JavaTemplate javaTemplate = JavaTemplate.builder(snippet).imports(imports.toArray(String[]::new)) + .javaParser(clone) + .build(); + md = javaTemplate.apply(getCursor(), md.getCoordinates().addAnnotation((a1, a2) -> Integer.valueOf(a1.getSimpleName().length()).compareTo(a2.getSimpleName().length()))); + imports.forEach(i -> maybeAddImport(i)); + } + return md; + } + }); + refactoring.refactor(sourceFile, visitor); + } + + @Override + public void addAnnotation(String snippet, String annotationImport, Set typeStubs, String... otherImports) { + // FIXME: #7 requires a fresh instance of JavaParser to update typesInUse + Recipe visitor = new GenericOpenRewriteRecipe<>(() -> new JavaIsoVisitor<>(){ + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { + J.MethodDeclaration md = super.visitMethodDeclaration(method, executionContext); + if(md == getMethodDecl()) { + J.CompilationUnit cu = getCursor().dropParentUntil(J.CompilationUnit.class::isInstance).getValue(); + + // FIXME: (jaxrs): Build JavaParser and use dependsOn to provide stubs for required types + JavaParser.Builder clone = JavaParser.fromJavaVersion().dependsOn(typeStubs.toArray(String[]::new)).clone(); + List imports = Stream.concat(Stream.of(annotationImport), Stream.of(otherImports)).toList(); + + System.out.println("Snippet: " + snippet); + JavaTemplate javaTemplate = JavaTemplate.builder(snippet).imports(imports.toArray(String[]::new)) .javaParser(clone) .build(); @@ -133,6 +162,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex refactoring.refactor(sourceFile, visitor); } + /** * {@inheritDoc} */ diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteMethodParam.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteMethodParam.java index 1facbfffc..657eaebd0 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteMethodParam.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteMethodParam.java @@ -29,6 +29,7 @@ import org.springframework.sbm.support.openrewrite.java.RemoveAnnotationVisitor; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteType.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteType.java index 8c2b3efbe..bc98a9c11 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteType.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/OpenRewriteType.java @@ -18,18 +18,13 @@ import lombok.extern.slf4j.Slf4j; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; import org.openrewrite.java.*; -import org.openrewrite.java.format.WrappingAndBraces; -import org.openrewrite.java.internal.JavaTypeCache; -import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.search.DeclaresMethod; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.ClassDeclaration; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.JavaType.Class; import org.openrewrite.java.tree.TypeUtils; -import org.openrewrite.marker.Markers; import org.springframework.rewrite.parser.maven.ClasspathDependencies; import org.springframework.rewrite.resource.RewriteSourceFileHolder; import org.springframework.rewrite.recipes.GenericOpenRewriteRecipe; @@ -37,13 +32,11 @@ import org.springframework.sbm.java.migration.visitor.RemoveImplementsVisitor; import org.springframework.sbm.java.refactoring.JavaRefactoring; import org.springframework.rewrite.parser.JavaParserBuilder; -import org.springframework.sbm.support.openrewrite.java.AddAnnotationVisitor; import org.springframework.sbm.support.openrewrite.java.FindCompilationUnitContainingType; import org.springframework.sbm.support.openrewrite.java.RemoveAnnotationVisitor; import java.nio.file.Path; import java.util.*; -import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -118,6 +111,7 @@ public void addAnnotation(String fqName) { */ @Override public void addAnnotation(String snippet, String annotationImport, String... otherImports) { + // FIXME: The ClasspathDependencies Marker makes this code incompatible to OpenRewrite Optional classpathDependencies = rewriteSourceFileHolder.getSourceFile().getMarkers().findFirst(ClasspathDependencies.class); List classpath = classpathDependencies.get().getDependencies(); @@ -128,6 +122,7 @@ public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, Execut ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext); if (cd == getClassDeclaration()) { List imports = Stream.concat(Stream.of(otherImports), Stream.of(annotationImport)).toList(); + // FIXME: Requires stubs for JAX-RS instead of types from jar JavaTemplate javaTemplate = JavaTemplate.builder(snippet).imports(imports.toArray(String[]::new)).javaParser(javaParserBuilder.classpath(classpath).clone()).build(); cd = javaTemplate.apply(getCursor(), getClassDeclaration().getCoordinates().addAnnotation((o1, o2) -> Integer.valueOf(o1.getSimpleName().length()).compareTo(o2.getSimpleName().length()))); imports.forEach(i -> maybeAddImport(i)); @@ -139,6 +134,32 @@ public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, Execut refactoring.refactor(rewriteSourceFileHolder, recipe); } + /** + * Does not rely on markers to resolve classpath but takes a list of type stubs instead. + */ + @Override + public void addAnnotation(String snippet, String annotationImport, Set typeStubs, String... otherImports) { + // FIXME: The ClasspathDependencies Marker makes this code incompatible to OpenRewrite + GenericOpenRewriteRecipe> recipe = new GenericOpenRewriteRecipe<>(() -> { + return new JavaIsoVisitor<>() { + @Override + public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext executionContext) { + ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext); + if (cd == getClassDeclaration()) { + List imports = Stream.concat(Stream.of(otherImports), Stream.of(annotationImport)).toList(); + // FIXME: Requires stubs for JAX-RS instead of types from jar + String[] classpath = typeStubs.toArray(String[]::new); + JavaTemplate javaTemplate = JavaTemplate.builder(snippet).imports(imports.toArray(String[]::new)).javaParser(javaParserBuilder.dependsOn(classpath).clone()).build(); + cd = javaTemplate.apply(getCursor(), getClassDeclaration().getCoordinates().addAnnotation((o1, o2) -> Integer.valueOf(o1.getSimpleName().length()).compareTo(o2.getSimpleName().length()))); + imports.forEach(i -> maybeAddImport(i)); + } + return cd; + } + }; + }); + refactoring.refactor(rewriteSourceFileHolder, recipe); + } + @Override public Annotation getAnnotation(String fqName) { return getAnnotations().stream() diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/ProjectJavaSourcesImpl.java b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/ProjectJavaSourcesImpl.java index 90a60e212..dba4a37ee 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/ProjectJavaSourcesImpl.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/java/impl/ProjectJavaSourcesImpl.java @@ -51,7 +51,7 @@ public ProjectJavaSourcesImpl(ProjectResourceSet filteredResources, JavaGlobalRe } @Override - public void apply(Recipe recipe) { + public void apply(Recipe... recipe) { globalRefactoring.refactor(recipe); } diff --git a/components/sbm-openrewrite/src/main/java/org/springframework/sbm/support/openrewrite/java/AddAnnotationVisitor.java b/components/sbm-openrewrite/src/main/java/org/springframework/sbm/support/openrewrite/java/AddAnnotationVisitor.java index 4626ea46a..c66afec89 100644 --- a/components/sbm-openrewrite/src/main/java/org/springframework/sbm/support/openrewrite/java/AddAnnotationVisitor.java +++ b/components/sbm-openrewrite/src/main/java/org/springframework/sbm/support/openrewrite/java/AddAnnotationVisitor.java @@ -33,7 +33,6 @@ public class AddAnnotationVisitor extends JavaIsoVisitor { private final J target; private final String snippet; private final String[] imports; - private final Supplier javaParserSupplier; // ugly, just because UUID of elemnts stay same now and can't be used as criteria leading to multiple visits of the same . private boolean targetVisited; @@ -41,13 +40,16 @@ public AddAnnotationVisitor(JavaParser.Builder javaParserSupplier, J target, Str this(() -> javaParserSupplier, target, snippet, annotationImport, otherImports); } + public AddAnnotationVisitor(J target, String snippet, String annotationImport, String... otherImports) { + this(() -> null, target, snippet, annotationImport, otherImports); + } + public AddAnnotationVisitor(Supplier javaParserSupplier, J target, String snippet, String annotationImport, String... otherImports) { this.target = target; this.snippet = snippet; this.imports = otherImports == null ? new String[]{annotationImport} : concat(annotationImport, otherImports); - this.javaParserSupplier = javaParserSupplier; } public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext p) { From 710cb59fa468727f18fc0c134b83d9eec1ca8122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 14:05:23 +0200 Subject: [PATCH 04/19] Fix retrieval of Validator --- .../sbm/engine/recipe/CustomValidator.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/CustomValidator.java b/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/CustomValidator.java index af578afc8..16749eaee 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/CustomValidator.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/CustomValidator.java @@ -15,12 +15,10 @@ */ package org.springframework.sbm.engine.recipe; +import jakarta.validation.*; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import jakarta.validation.ConstraintViolation; -import jakarta.validation.ValidationException; -import jakarta.validation.Validator; import java.util.Set; import java.util.stream.Collectors; @@ -28,9 +26,9 @@ @RequiredArgsConstructor public class CustomValidator { - private final Validator validator; - void validate(Recipe recipe) { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + Validator validator = factory.getValidator(); final Set> violations = validator.validate(recipe); if (!violations.isEmpty()) { String message = String.format( From bf1086758469ce6ca00bd401ae1d2f8ad414399b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 14:07:34 +0200 Subject: [PATCH 05/19] Organize imports --- .../jaxrs/recipes/ResponseEntityReplacementTest.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java index b9f262c6d..79b82a59a 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -15,19 +15,11 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; -import org.openrewrite.java.JavaParser; -import org.springframework.rewrite.parser.RewriteExecutionContext; -import org.springframework.rewrite.parser.SpringRewriteProperties; -import org.springframework.sbm.engine.recipe.AbstractAction; +import org.junit.jupiter.api.Test; import org.springframework.sbm.engine.context.ProjectContext; -import org.springframework.sbm.java.impl.RewriteJavaParser; -import org.springframework.sbm.project.resource.SbmApplicationProperties; +import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; -import org.junit.jupiter.api.Test; -import org.openrewrite.Recipe; - -import java.util.function.Supplier; import static org.assertj.core.api.Assertions.assertThat; From de970dfa0d8b617f82328ef4e60837d52b626ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 14:10:23 +0200 Subject: [PATCH 06/19] Add misisng package --- .../jsf/actions/AddJoinfacesDependencies.java | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/actions/AddJoinfacesDependencies.java diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/actions/AddJoinfacesDependencies.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/actions/AddJoinfacesDependencies.java new file mode 100644 index 000000000..4a7c8aa97 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/actions/AddJoinfacesDependencies.java @@ -0,0 +1,147 @@ +/* + * Copyright 2021 - 2023 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.jsf.actions; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.sbm.build.MultiModuleApplicationNotSupportedException; +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.build.api.Module; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.AbstractAction; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class AddJoinfacesDependencies extends AbstractAction { + + @Override + public void apply(ProjectContext context) { + if (context.getApplicationModules().isSingleModuleApplication()) { + Module module = context.getApplicationModules().getRootModule(); + applyToModule(module); + } else { + throw new MultiModuleApplicationNotSupportedException("Action can only be applied to applications with single module."); + } + } + + private void applyToModule(Module module) { + JsfImplementation jsfImplementation = getJsfImplementationInUse(module); + + if (jsfImplementation.equals(JsfImplementation.UNKNOWN)) { + log.warn("Could not find used JSF implementation. Currently supported implementations are [" + JsfImplementation.supportedImplementations() + "]"); + return; + } + + addJoinfacesDependencyManagement(module); + addJoinfacesDependencies(jsfImplementation, module); + } + + @Override + public boolean isApplicable(ProjectContext context) { + if (context.getApplicationModules().isSingleModuleApplication()) { + return hasJsfImport(context.getApplicationModules().getRootModule()); + } + return false; + } + + private void addJoinfacesDependencies(JsfImplementation jsfImplementation, Module module) { + if (jsfImplementation.equals(JsfImplementation.APACHE_MYFACES)) { + addMyFacesDependencies(module); + } else if (jsfImplementation.equals(JsfImplementation.MOJARRA)) { + addMojarraDependencies(module); + } + } + + private void addMojarraDependencies(Module context) { + Dependency joinfacesStarter = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("jsf-spring-boot-starter") + .version("4.4.10") + .build(); + + context.getBuildFile().addDependencies(List.of(joinfacesStarter)); + } + + private void addMyFacesDependencies(Module context) { + Dependency joinfacesStarter = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("jsf-spring-boot-starter") + .version("4.4.10") + .exclusions(List.of(Dependency.builder() + .groupId("org.joinfaces") + .artifactId("mojarra-spring-boot-starter") + .build()) + ) + .build(); + + Dependency myfacesStarter = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("myfaces-spring-boot-starter") + .version("4.4.10") + .build(); + + context.getBuildFile().addDependencies(List.of(joinfacesStarter, myfacesStarter)); + } + + private void addJoinfacesDependencyManagement(Module context) { + Dependency joinfacesDependencyManagement = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("joinfaces-dependencies") + .version("4.4.10") + .type("pom") + .scope("import") + .build(); + context.getBuildFile().addToDependencyManagement(joinfacesDependencyManagement); + } + + private JsfImplementation getJsfImplementationInUse(Module module) { + if (usesMyFaces(module)) { + return JsfImplementation.APACHE_MYFACES; + } else if (usesMojarra(module)) { + return JsfImplementation.MOJARRA; + } else { + return JsfImplementation.UNKNOWN; + } + } + + private boolean usesMojarra(Module module) { + return module.getBuildFile().hasDeclaredDependencyMatchingRegex( + "org\\.glassfish\\:javax\\.faces.*", + "org\\.glassfish\\:jakarta\\.faces.*"); + } + + private boolean usesMyFaces(Module module) { + return module.getBuildFile().hasDeclaredDependencyMatchingRegex("org\\.apache\\.myfaces.*"); + } + + private boolean hasJsfImport(Module module) { + return module.getMainJavaSourceSet().hasImportStartingWith("javax.faces"); + } + + private enum JsfImplementation { + APACHE_MYFACES, + MOJARRA, + UNKNOWN; + + public static String supportedImplementations() { + return Arrays.asList(JsfImplementation.values()).stream() + .map(jsfimpl -> jsfimpl.name()) + .collect(Collectors.joining(", ")); + } + } +} From 44b4b000ff5072ce81c3de79f6ca89f67e03c95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 14:19:32 +0200 Subject: [PATCH 07/19] Fix recipe --- .../jee/jaxrs/recipes/SwapResponseWithResponseEntity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java index 54ee2a28b..a5f16dd95 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java @@ -37,7 +37,8 @@ public class SwapResponseWithResponseEntity extends Recipe { - public SwapResponseWithResponseEntity() { + @Override + public List getRecipeList() { List recipeList = new ArrayList<>(); recipeList.add(new SwapStatusForHttpStatus()); @@ -365,6 +366,8 @@ public SwapResponseWithResponseEntity() { recipeList.add(new ReplaceResponseEntityBuilder()); recipeList.add(new ChangeType("javax.ws.rs.core.Response", "org.springframework.http.ResponseEntity", false)); + + return recipeList; } private void markTopLevelInvocationWithTemplate(JavaVisitor v, MethodInvocation m, String template) { From 1dc9e8f785403bbab5514e889dba44767608623c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 14:19:54 +0200 Subject: [PATCH 08/19] Use multiline String --- .../ResponseEntityReplacementTest.java | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java index 79b82a59a..d3aa05025 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -85,27 +85,25 @@ void testUnsupportedStaticCall() { @Test void testUnsupportedBuilderCall() { - String javaSource = "" - + "import javax.ws.rs.core.Response;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public Response respond() {\n" - + " return Response.status(200).tag(\"My Tag\").build();\n" - + " }\n" - + "}\n" - + ""; - - String expected = "" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity respond() {\n" - + " return ResponseEntity.status(200).eTag(\"My Tag\").build();\n" - + " }\n" - + "}\n" - + ""; + String javaSource = """ + import javax.ws.rs.core.Response; + + public class TestController { + public Response respond() { + return Response.status(200).tag("My Tag").build(); + } + } + """; + + String expected = """ + import org.springframework.http.ResponseEntity; + + public class TestController { + public ResponseEntity respond() { + return ResponseEntity.status(200).eTag("My Tag").build(); + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( From fd44a40a7e24e06eed6db191260a821e48d48d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 14:48:24 +0200 Subject: [PATCH 09/19] Fix tests --- .../sbm/JaxRsThroughAdapterTest.java | 160 ++++++++++-------- .../jaxrs/recipes/ResponseBuilderTest.java | 66 ++++---- .../ResponseEntityReplacementTest.java | 51 +++--- ...teJsf2ToSpringBootApplicableCondition.java | 41 +++++ .../actions/MoveFilesActionTest.java | 1 - ...licationModules_getTopmostModulesTest.java | 40 +++-- .../project/buildfile/AddDependencyTest.java | 7 + .../project/resource/TestProjectContext.java | 5 +- .../sbm/test/SpringBeanProvider.java | 4 +- 9 files changed, 223 insertions(+), 152 deletions(-) create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/conditions/IsMigrateJsf2ToSpringBootApplicableCondition.java diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java index 343cb3e2c..5c45f85f2 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.SourceFile; import org.openrewrite.config.Environment; import org.openrewrite.java.Assertions; import org.openrewrite.java.JavaParser; @@ -45,12 +46,15 @@ public class JaxRsThroughAdapterTest { @Nested class WithRewriteTest implements RewriteTest { + + // FIXME: Use Openrewrite to retrieve dependency Paths that must be on classpath + // mvn dependency:build-classpath -Dmdep.outputFile=cp.txt + List deps = Arrays.stream("/Users/fkrueger/.m2/repository/org/jboss/spec/javax/ws/rs/jboss-jaxrs-api_2.1_spec/1.0.1.Final/jboss-jaxrs-api_2.1_spec-1.0.1.Final.jar".split(":")).map(d -> Path.of(d)).toList(); + private JavaParser.Builder javaParserBuilder = JavaParser.fromJavaVersion().classpath(deps); + @Override public void defaults(RecipeSpec spec) { - // mvn dependency:build-classpath -Dmdep.outputFile=cp.txt - List deps = Arrays.stream("/Users/fkrueger/.m2/repository/org/jboss/spec/javax/ws/rs/jboss-jaxrs-api_2.1_spec/1.0.1.Final/jboss-jaxrs-api_2.1_spec-1.0.1.Final.jar".split(":")).map(d -> Path.of(d)).toList(); - - spec.parser(JavaParser.fromJavaVersion().classpath(deps)); + spec.parser(javaParserBuilder); String RECIPE = "example.recipe.SbmAdapterRecipe"; spec.recipe(Environment.builder() @@ -61,10 +65,12 @@ public void defaults(RecipeSpec spec) { @Test public void theTest() { + rewriteRun( (spec) -> spec.expectedCyclesThatMakeChanges(2), mavenProject("project", Assertions.java( + //language=Java """ package com.example.jee.app; @@ -112,6 +118,7 @@ private boolean isResponseStatusSuccessful(Response.Status.Family family) { } """, + //language=Java """ package com.example.jee.app; @@ -161,75 +168,78 @@ private boolean isResponseStatusSuccessful(Response.Status.Family family) { pomXml( //language=xml """ - - - 4.0.0 - org.springframework.sbm.examples - migrate-jax-rs - jar - 0.0.1-SNAPSHOT - - UTF-8 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - - - - - - - org.jboss.spec.javax.ws.rs - jboss-jaxrs-api_2.1_spec - 1.0.1.Final - - - - - jcenter - jcenter - https://jcenter.bintray.com - - - mavencentral - mavencentral - https://repo.maven.apache.org/maven2 - - - - """ + + + 4.0.0 + org.springframework.sbm.examples + migrate-jax-rs + jar + 0.0.1-SNAPSHOT + + UTF-8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + + + org.jboss.spec.javax.ws.rs + jboss-jaxrs-api_2.1_spec + 1.0.1.Final + + + + + jcenter + jcenter + https://jcenter.bintray.com + + + mavencentral + mavencentral + https://repo.maven.apache.org/maven2 + + + + """ ) ) ); } } - - @Test - @DisplayName("jax-rs recipes through adapter in openrewrite") - void jaxRsRecipesThroughAdapterInOpenrewrite(@TempDir Path tmpDir) throws IOException { - - String projectRootDir = "jee/jaxrs/bootify-jaxrs"; - Path from = Path.of("./testcode").toAbsolutePath().normalize().resolve(projectRootDir).resolve("given"); - Path to = Path.of("./target/test-projects/").resolve(projectRootDir).toAbsolutePath().normalize(); - if(Files.exists(to)) { + + @Nested + class WithRewritePlugin { + + @Test + @DisplayName("jax-rs recipes through adapter in openrewrite") + void jaxRsRecipesThroughAdapterInOpenrewrite(@TempDir Path tmpDir) throws IOException { + + String projectRootDir = "jee/jaxrs/bootify-jaxrs"; + Path from = Path.of("./testcode").toAbsolutePath().normalize().resolve(projectRootDir).resolve("given"); + Path to = Path.of("./target/test-projects/").resolve(projectRootDir).toAbsolutePath().normalize(); + if (Files.exists(to)) { + FileUtils.deleteDirectory(to.toFile()); + } + Files.createDirectories(to); FileUtils.deleteDirectory(to.toFile()); - } - Files.createDirectories(to); - FileUtils.deleteDirectory(to.toFile()); - FileUtils.forceMkdir(to.toFile()); - FileUtils.copyDirectory(from.toFile(), to.toFile()); + FileUtils.forceMkdir(to.toFile()); + FileUtils.copyDirectory(from.toFile(), to.toFile()); - String mavenPluginVersion = ""; - String gradlePluginVersion = ""; - Path baseDir = tmpDir; + String mavenPluginVersion = ""; + String gradlePluginVersion = ""; + Path baseDir = tmpDir; // mvn -B --fail-at-end org.openrewrite.maven:rewrite-maven-plugin:5.32.1:dryRun \ // -Drewrite.activeRecipes=example.recipe.SbmAdapterRecipe \  @@ -237,18 +247,20 @@ void jaxRsRecipesThroughAdapterInOpenrewrite(@TempDir Path tmpDir) throws IOExce // -Dmaven.opts=“-Xms256M -Xmx1024M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005“ - PluginInvocationResult pluginInvocationResult = RewritePlugin.dryRun() - .mavenPluginVersion(mavenPluginVersion) - .gradlePluginVersion(gradlePluginVersion) - // TODO: Add method to provide path to compiled classes - .recipes("example.recipe.SbmAdapterRecipe") - .dependencies("org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT") + PluginInvocationResult pluginInvocationResult = RewritePlugin.run() + .mavenPluginVersion(mavenPluginVersion) + .gradlePluginVersion(gradlePluginVersion) + // TODO: Add method to provide path to compiled classes + .recipes("example.recipe.SbmAdapterRecipe") + .dependencies("org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT") // .withDebugging(5005, true) // .withDebug() - .onDir(to); + .onDir(to); - System.out.println(pluginInvocationResult.capturedOutput()); + System.out.println(pluginInvocationResult.capturedOutput()); + } } + } diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java index 713197a31..03effcb5d 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java @@ -237,39 +237,39 @@ void lastModified() { @Test void replaceAll() { - String javaSource = "" - + "import javax.ws.rs.core.MultivaluedMap;\n" - + "import javax.ws.rs.core.Response.ResponseBuilder;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseBuilder test() {\n" - + " ResponseBuilder b;\n" - + " MultivaluedMap m;\n" - + " b.replaceAll(m);\n" - + " return b;\n" - + " }\n" - + "}\n" - + ""; - - String expected = "" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "import javax.ws.rs.core.MultivaluedMap;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity.BodyBuilder test() {\n" - + " ResponseEntity.BodyBuilder b;\n" - + " MultivaluedMap m;\n" - + " b.headers(h -> {\n" - + " h.clear();\n" - + " h.addAll(m);\n" - + " });\n" - + " return b;\n" - + " }\n" - + "}\n" - + ""; + String javaSource = """ + import javax.ws.rs.core.MultivaluedMap; + import javax.ws.rs.core.Response.ResponseBuilder; + + public class TestController { + + public ResponseBuilder test() { + ResponseBuilder b; + MultivaluedMap m; + b.replaceAll(m); + return b; + } + } + """; + + String expected = """ + import org.springframework.http.ResponseEntity; + + import javax.ws.rs.core.MultivaluedMap; + + public class TestController { + + public ResponseEntity.BodyBuilder test() { + ResponseEntity.BodyBuilder b; + MultivaluedMap m; + b.headers(h -> { + h.clear(); + h.addAll(m); + }); + return b; + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java index d3aa05025..adabc0a26 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -714,33 +714,32 @@ void serverError() { @Test void temporaryRedirect() { - String javaSource = "" - + "import javax.ws.rs.core.Response;\n" - + "import java.net.URI;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public Response respond() {\n" - + " URI uri = URI.create(\"https://spring.io\");\n" - + " return Response.temporaryRedirect(uri).build();\n" - + " }\n" - + "}\n" - + ""; + String javaSource = """ + import javax.ws.rs.core.Response; + import java.net.URI; + + public class TestController { + + public Response respond() { + URI uri = URI.create("https://spring.io"); + return Response.temporaryRedirect(uri).build(); + } + } + """; - String expected = "" - + "import org.springframework.http.HttpStatus;\n" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "import java.net.URI;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity respond() {\n" - + " URI uri = URI.create(\"https://spring.io\");\n" - + " return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(uri).build();\n" - + " }\n" - + "}\n" - + ""; + String expected = """ + import org.springframework.http.HttpStatus; + import org.springframework.http.ResponseEntity; + import java.net.URI; + + public class TestController { + + public ResponseEntity respond() { + URI uri = URI.create("https://spring.io"); + return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(uri).build(); + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/conditions/IsMigrateJsf2ToSpringBootApplicableCondition.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/conditions/IsMigrateJsf2ToSpringBootApplicableCondition.java new file mode 100644 index 000000000..f13d56bee --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jsf/conditions/IsMigrateJsf2ToSpringBootApplicableCondition.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 - 2023 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.jsf.conditions; + +import org.springframework.sbm.build.api.Dependency; +import org.springframework.sbm.build.migration.conditions.NoExactDependencyExist; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.Condition; +import org.springframework.sbm.java.migration.conditions.HasImportStartingWith; + +public class IsMigrateJsf2ToSpringBootApplicableCondition implements Condition { + @Override + public String getDescription() { + return "Check if recipe 'migrate-jsf-2.x-to-spring-boot' is applicable"; + } + + @Override + public boolean evaluate(ProjectContext context) { + Dependency dependency = Dependency.builder() + .groupId("org.joinfaces") + .artifactId("jsf-spring-boot-starter") + .build(); + NoExactDependencyExist noExactDependencyExistCondition = new NoExactDependencyExist(dependency); + HasImportStartingWith hasImportStartingWithCondition = new HasImportStartingWith("javax.faces", "Search for imports starting with 'javax.faces'"); + + return noExactDependencyExistCondition.evaluate(context) && hasImportStartingWithCondition.evaluate(context); + } +} diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/common/migration/actions/MoveFilesActionTest.java b/components/sbm-core/src/test/java/org/springframework/sbm/common/migration/actions/MoveFilesActionTest.java index 41bf94c3a..ffe5d7ff4 100644 --- a/components/sbm-core/src/test/java/org/springframework/sbm/common/migration/actions/MoveFilesActionTest.java +++ b/components/sbm-core/src/test/java/org/springframework/sbm/common/migration/actions/MoveFilesActionTest.java @@ -30,7 +30,6 @@ class MoveFilesActionTest { // TODO: test moving outside project root - @Test @DisplayName("Move two files to a new dir") void moveFile() { diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/project/ApplicationModules_getTopmostModulesTest.java b/components/sbm-core/src/test/java/org/springframework/sbm/project/ApplicationModules_getTopmostModulesTest.java index 0ad2c7dee..137fed9f6 100644 --- a/components/sbm-core/src/test/java/org/springframework/sbm/project/ApplicationModules_getTopmostModulesTest.java +++ b/components/sbm-core/src/test/java/org/springframework/sbm/project/ApplicationModules_getTopmostModulesTest.java @@ -72,19 +72,33 @@ void whenGetTopmostApplicationModulesThenChildModuleShouldBeReturned() { @Nested public class GivenTwoModuleProject { private static final String parentPom = - "\n" + - "\n" + - " com.example.sbm\n" + - " parent\n" + - " 0.1.0-SNAPSHOT\n" + - " 4.0.0\n" + - " pom\n" + - " \n" + - " module1\n" + - " " + - "\n"; + """ + + + com.example.sbm + parent + 0.1.0-SNAPSHOT + 4.0.0 + pom + + module1 + + + + org.hibernate + hibernate-validator + 7.0.2.Final + + + jakarta.validation + jakarta.validation-api + 3.0.1 + + + + """; private static final String childModule = "\n" + diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/project/buildfile/AddDependencyTest.java b/components/sbm-core/src/test/java/org/springframework/sbm/project/buildfile/AddDependencyTest.java index d9501f6fe..0e9bea505 100644 --- a/components/sbm-core/src/test/java/org/springframework/sbm/project/buildfile/AddDependencyTest.java +++ b/components/sbm-core/src/test/java/org/springframework/sbm/project/buildfile/AddDependencyTest.java @@ -148,6 +148,13 @@ public class AddDependencyTest { @Autowired ExecutionContext executionContext; + @Test + @DisplayName("start context") + void startContext() { + System.out.println("yes"); + + } + /** * */ diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/project/resource/TestProjectContext.java b/components/sbm-core/src/test/java/org/springframework/sbm/project/resource/TestProjectContext.java index c6b019aa8..bb4addef4 100644 --- a/components/sbm-core/src/test/java/org/springframework/sbm/project/resource/TestProjectContext.java +++ b/components/sbm-core/src/test/java/org/springframework/sbm/project/resource/TestProjectContext.java @@ -702,9 +702,8 @@ private ProjectContextInitializer createProjectContextInitializer() { }, replacedBean, RewriteLauncherConfiguration.class, - SpringBeanProvider.ComponentScanConfiguration.class, - Configuration.class, - CustomValidatorBean.class); + SpringBeanProvider.ComponentScanConfiguration.class + ); } return projectContextInitializerRef.get(); } diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/test/SpringBeanProvider.java b/components/sbm-core/src/test/java/org/springframework/sbm/test/SpringBeanProvider.java index 900ccb320..1092ab61b 100644 --- a/components/sbm-core/src/test/java/org/springframework/sbm/test/SpringBeanProvider.java +++ b/components/sbm-core/src/test/java/org/springframework/sbm/test/SpringBeanProvider.java @@ -37,7 +37,7 @@ public class SpringBeanProvider { @Configuration - @ComponentScan(value = "org.springframework.sbm", excludeFilters = @ComponentScan.Filter(classes = TestConfiguration.class)) + @ComponentScan(value = {"org.springframework.sbm", "org.springframework.rewrite"}, excludeFilters = @ComponentScan.Filter(classes = TestConfiguration.class)) public static class ComponentScanConfiguration { } public static void run(ContextConsumer testcode, Class... springBeans) { @@ -80,7 +80,7 @@ private Optional findReplacementForBean(Map, Object> replacedBe // annotationConfigApplicationContext.scan("org.springframework.sbm", "org.springframework.freemarker"); annotationConfigApplicationContext.refresh(); if (new File("./src/main/resources/templates").exists()) { - freemarker.template.Configuration configuration = annotationConfigApplicationContext.getBean("configuration", freemarker.template.Configuration.class); // FIXME: two freemarker configurations exist + freemarker.template.Configuration configuration = annotationConfigApplicationContext.getBean(freemarker.template.Configuration.class); // FIXME: two freemarker configurations exist try { configuration.setDirectoryForTemplateLoading(new File("./src/main/resources/templates")); } catch (IOException e) { From 9f9e37cb52573e1ab2774471345cde8c0a3d4926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 14:49:33 +0200 Subject: [PATCH 10/19] Fix tests --- .../ResponseEntityReplacementTest.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java index adabc0a26..ddbe09f41 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -369,28 +369,28 @@ void testReplaceOkWithMediaTypeAndBody() { @Test void testReplaceOkWithMediaTypeStringAndBody() { - String javaSource = "" - + "import javax.ws.rs.core.Response;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public Response respond() {\n" - + " return Response.ok(\"All good!\", \"application/json\").build();\n" - + " }\n" - + "}\n" - + ""; + String javaSource = """ + import javax.ws.rs.core.Response; + + public class TestController { + + public Response respond() { + return Response.ok("All good!", "application/json").build(); + } + } + """; - String expected = "" - + "import org.springframework.http.MediaType;\n" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity respond() {\n" - + " return ResponseEntity.ok().contentType(MediaType.parseMediaType(\"application/json\")).body(\"All good!\");\n" - + " }\n" - + "}\n" - + ""; + String expected = """ + import org.springframework.http.MediaType; + import org.springframework.http.ResponseEntity; + + public class TestController { + + public ResponseEntity respond() { + return ResponseEntity.ok().contentType(MediaType.parseMediaType("application/json")).body("All good!"); + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( From 06ef1195bc2135237dbede0a38e9f2b4dcf19c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 15:00:28 +0200 Subject: [PATCH 11/19] Fix tests --- .../CopyAnnotationAttributeVisitor.java | 8 +- .../jaxrs/recipes/ReplaceMediaTypeTest.java | 4 +- .../jaxrs/recipes/ResponseBuilderTest.java | 8 +- .../ResponseEntityReplacementTest.java | 255 +++++++++--------- 4 files changed, 135 insertions(+), 140 deletions(-) diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java index 90e59b492..e59f294ff 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/visitors/CopyAnnotationAttributeVisitor.java @@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; +import org.openrewrite.TreeVisitor; import org.openrewrite.java.AddOrUpdateAnnotationAttribute; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.J; @@ -62,12 +63,11 @@ public J.Annotation visitAnnotation(@NotNull J.Annotation annotation, @NotNull E 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 = (JavaIsoVisitor) new AddOrUpdateAnnotationAttribute(targetAnnotationType, targetAttributeName, targetAttributeValue, false) - .getVisitor(); + TreeVisitor visitor = new AddOrUpdateAnnotationAttribute(targetAnnotationType, targetAttributeName, targetAttributeValue, false).getVisitor(); if (targetAnnotationOnlyHasOneLiteralArgument(a)) { - a = (J.Annotation) addOrUpdateAnnotationAttributeVisitor.visit(a, ctx, getCursor()); + a = (J.Annotation) visitor.visit(a, ctx, getCursor()); } - return (J.Annotation) addOrUpdateAnnotationAttributeVisitor.visit(a, ctx, getCursor()); + return (J.Annotation) visitor.visit(a, ctx, getCursor()); } return a; } diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java index 4650e8fe2..abafef10c 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java @@ -485,9 +485,8 @@ public MediaType respond() { """; String expected = """ - import org.springframework.http.MediaType; - import java.util.Map; + import org.springframework.http.MediaType; public class TestController { @@ -537,7 +536,6 @@ public String getHelloWorldJSON(@PathParam("name") String name) { """ import javax.ws.rs.Path; import javax.ws.rs.PathParam; - import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java index 03effcb5d..1276ec3e6 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java @@ -63,7 +63,6 @@ void allow() { String expected = "" + "import java.util.Set;\n" - + "\n" + "import org.springframework.http.HttpMethod;\n" + "import org.springframework.http.ResponseEntity;\n" + "\n" @@ -112,9 +111,8 @@ void expires() { + ""; String expected = "" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" + "import java.util.Date;\n" + + "import org.springframework.http.ResponseEntity;\n" + "\n" + "public class TestController {\n" + "\n" @@ -159,7 +157,6 @@ void language() { String expected = "" + "import java.util.Locale;\n" - + "\n" + "import org.springframework.http.HttpHeaders;\n" + "import org.springframework.http.ResponseEntity;\n" + "\n" @@ -253,9 +250,8 @@ public ResponseBuilder test() { """; String expected = """ - import org.springframework.http.ResponseEntity; - import javax.ws.rs.core.MultivaluedMap; + import org.springframework.http.ResponseEntity; public class TestController { diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java index ddbe09f41..bc7333ad6 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -21,6 +21,8 @@ import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; +import java.util.stream.Collectors; + import static org.assertj.core.api.Assertions.assertThat; public class ResponseEntityReplacementTest { @@ -509,7 +511,6 @@ void created() { String expected = "" + "import org.springframework.http.ResponseEntity;\n" - + "\n" + "import java.net.URI;\n" + "\n" + "public class TestController {\n" @@ -641,7 +642,6 @@ void seeOther() { String expected = "" + "import org.springframework.http.HttpStatus;\n" + "import org.springframework.http.ResponseEntity;\n" - + "\n" + "import java.net.URI;\n" + "\n" + "public class TestController {\n" @@ -759,87 +759,87 @@ public ResponseEntity respond() { @Test void instanceMethods() { - String javaSource = "" - + "import javax.ws.rs.core.Response;\n" - + "import javax.ws.rs.core.GenericType;\n" - + "import java.lang.annotation.Annotation;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public String respond() {\n" - + " Response r = Response.ok().build();\n" - + " r.getAllowedMethods();\n" - + " r.bufferEntity();\n" - + " r.close();\n" - + " r.getCookies();\n" - + " r.getDate();\n" - + " r.getEntity();\n" - + " r.bufferEntity();\n" - + " r.getEntityTag();\n" - + " r.getHeaders();\n" - + " r.getHeaderString(\"Accept\");\n" - + " r.getLanguage();\n" - + " r.getLastModified();\n" - + " r.getLength();\n" - + " r.getLink(\"Something\");\n" - + " r.getLinkBuilder(\"Something\");\n" - + " r.getLinks();\n" - + " r.getLocation();\n" - + " r.getMediaType();\n" - + " r.getMetadata();\n" - + " r.getStatus();\n" - + " r.getStatusInfo();\n" - + " r.getStringHeaders();\n" - + " r.hasEntity();\n" - + " r.hasLink(\"Something\");\n" - + " r.readEntity(String.class, new Annotation[0]);\n" - + " r.readEntity(GenericType.forInstance(\"Something\"));\n" - + " r.readEntity(GenericType.forInstance(\"Something\"), new Annotation[0]);\n" - + " return r.readEntity(String.class);\n" - + " }\n" - + "}\n" - + ""; + String javaSource = """ + import javax.ws.rs.core.Response; + import javax.ws.rs.core.GenericType; + import java.lang.annotation.Annotation; + + public class TestController { + + public String respond() { + Response r = Response.ok().build(); + r.getAllowedMethods(); + r.bufferEntity(); + r.close(); + r.getCookies(); + r.getDate(); + r.getEntity(); + r.bufferEntity(); + r.getEntityTag(); + r.getHeaders(); + r.getHeaderString("Accept"); + r.getLanguage(); + r.getLastModified(); + r.getLength(); + r.getLink("Something"); + r.getLinkBuilder("Something"); + r.getLinks(); + r.getLocation(); + r.getMediaType(); + r.getMetadata(); + r.getStatus(); + r.getStatusInfo(); + r.getStringHeaders(); + r.hasEntity(); + r.hasLink("Something"); + r.readEntity(String.class, new Annotation[0]); + r.readEntity(GenericType.forInstance("Something")); + r.readEntity(GenericType.forInstance("Something"), new Annotation[0]); + return r.readEntity(String.class); + } + } + """; - String expected = "" - + "import org.springframework.http.ResponseEntity;\n" - + "import java.util.Date;\n" - + "import java.util.stream.Collectors;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public String respond() {\n" - + " ResponseEntity r = ResponseEntity.ok().build();\n" - + " r.getHeaders().getAllow().stream().map(m -> m.toString()).collect(Collectors.toList());\n" - + " r.bufferEntity();\n" - + " r.close();\n" - + " r.getCookies();\n" - + " new Date(r.getHeaders().getDate());\n" - + " r.getBody();\n" - + " r.bufferEntity();\n" - + " r.getHeaders().getETag();\n" - + " r.getHeaders();\n" - + " r.getHeaders().get(\"Accept\").stream().collect(Collectors.joining(\", \"));\n" - + " r.getHeaders().getContentLanguage();\n" - + " new Date(r.getHeaders().getLastModified());\n" - + " r.getHeaders().getContentLength();\n" - + " r.getLink(\"Something\");\n" - + " r.getLinkBuilder(\"Something\");\n" - + " r.getLinks();\n" - + " r.getHeaders().getLocation();\n" - + " r.getHeaders().getContentType();\n" - + " r.getHeaders();\n" - + " r.getStatusCodeValue();\n" - + " r.getStatusCode();\n" - + " r.getHeaders();\n" - + " r.hasBody();\n" - + " r.hasLink(\"Something\");\n" - + " r.getBody();\n" - + " r.getBody();\n" - + " r.getBody();\n" - + " return r.getBody();\n" - + " }\n" - + "}\n" - + ""; + String expected = """ + import org.springframework.http.ResponseEntity; + import java.util.Date; + import java.util.stream.Collectors; + + public class TestController { + + public String respond() { + ResponseEntity r = ResponseEntity.ok().build(); + r.getHeaders().getAllow().stream().map(m -> m.toString()).collect(Collectors.toList()); + r.bufferEntity(); + r.close(); + r.getCookies(); + new Date(r.getHeaders().getDate()); + r.getBody(); + r.bufferEntity(); + r.getHeaders().getETag(); + r.getHeaders(); + r.getHeaders().get("Accept").stream().collect(Collectors.joining(", ")); + r.getHeaders().getContentLanguage(); + new Date(r.getHeaders().getLastModified()); + r.getHeaders().getContentLength(); + r.getLink("Something"); + r.getLinkBuilder("Something"); + r.getLinks(); + r.getHeaders().getLocation(); + r.getHeaders().getContentType(); + r.getHeaders(); + r.getStatusCodeValue(); + r.getStatusCode(); + r.getHeaders(); + r.hasBody(); + r.hasLink("Something"); + r.getBody(); + r.getBody(); + r.getBody(); + return r.getBody(); + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( @@ -859,29 +859,30 @@ void instanceMethods() { @Test void chain_1() { - String javaSource = "" - + "import javax.ws.rs.core.Response;\n" - + "import javax.ws.rs.core.MediaType;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public Response respond() {\n" - + " return Response.status(200).entity(\"Hello\").tag(\"My Tag\").type(MediaType.TEXT_PLAIN_TYPE).build();\n" - + " }\n" - + "}\n" - + ""; + String javaSource = """ + import javax.ws.rs.core.Response; + import javax.ws.rs.core.MediaType; + + public class TestController { + + public Response respond() { + return Response.status(200).entity("Hello").tag("My Tag").type(MediaType.TEXT_PLAIN_TYPE).build(); + } + } + """; - String expected = "" - + "import org.springframework.http.MediaType;\n" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity respond() {\n" - + " return ResponseEntity.status(200).eTag(\"My Tag\").contentType(MediaType.TEXT_PLAIN).body(\"Hello\");\n" - + " }\n" - + "}\n" - + ""; + String expected = """ + import org.springframework.http.MediaType; + + import org.springframework.http.ResponseEntity; + + public class TestController { + + public ResponseEntity respond() { + return ResponseEntity.status(200).eTag("My Tag").contentType(MediaType.TEXT_PLAIN).body("Hello"); + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( @@ -901,30 +902,30 @@ void chain_1() { @Test void chain_2() { - String javaSource = "" - + "import javax.ws.rs.core.Response;\n" - + "import javax.ws.rs.core.Response.ResponseBuilder;\n" - + "import javax.ws.rs.core.MediaType;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseBuilder respond() {\n" - + " return Response.status(200).entity(\"Hello\").tag(\"My Tag\").type(MediaType.TEXT_PLAIN_TYPE);\n" - + " }\n" - + "}\n" - + ""; + String javaSource = """ + import javax.ws.rs.core.Response; + import javax.ws.rs.core.Response.ResponseBuilder; + import javax.ws.rs.core.MediaType; + + public class TestController { + + public ResponseBuilder respond() { + return Response.status(200).entity("Hello").tag("My Tag").type(MediaType.TEXT_PLAIN_TYPE); + } + } + """; - String expected = "" - + "import org.springframework.http.MediaType;\n" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity respond() {\n" - + " return ResponseEntity.status(200).eTag(\"My Tag\").contentType(MediaType.TEXT_PLAIN).body(\"Hello\");\n" - + " }\n" - + "}\n" - + ""; + String expected = """ + import org.springframework.http.MediaType; + import org.springframework.http.ResponseEntity; + + public class TestController { + + public ResponseEntity respond() { + return ResponseEntity.status(200).eTag("My Tag").contentType(MediaType.TEXT_PLAIN).body("Hello"); + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( From 76307d58a481efcf3f650488977c819cd4fdca40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 15:01:24 +0200 Subject: [PATCH 12/19] Fix tests --- .../sbm/jee/jaxrs/recipes/ResponseBuilderTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java index 1276ec3e6..2cc83dbde 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java @@ -204,9 +204,8 @@ void lastModified() { + ""; String expected = "" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" + "import java.util.Date;\n" + + "import org.springframework.http.ResponseEntity;\n" + "\n" + "public class TestController {\n" + "\n" From 68ae702839fbfd9202224b8c91dce74975fb7a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 16:55:25 +0200 Subject: [PATCH 13/19] Upgrade OR --- components/jaxrs-recipes/pom.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/jaxrs-recipes/pom.xml b/components/jaxrs-recipes/pom.xml index 573b90406..bad84cf2f 100644 --- a/components/jaxrs-recipes/pom.xml +++ b/components/jaxrs-recipes/pom.xml @@ -58,6 +58,13 @@ pom import + + org.openrewrite.recipe + rewrite-recipe-bom + 2.14.0 + pom + import + @@ -73,12 +80,10 @@ org.openrewrite rewrite-core - ${openrewrite.version} org.openrewrite rewrite-java - ${openrewrite.version} From 1200800974ac59ce3cfdd17773a5ed0b6318c501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 17:25:24 +0200 Subject: [PATCH 14/19] WIP --- components/jaxrs-recipes/pom.xml | 51 ++-- .../sbm/JaxRsThroughAdapterTest.java | 272 +++++++++++------- 2 files changed, 210 insertions(+), 113 deletions(-) diff --git a/components/jaxrs-recipes/pom.xml b/components/jaxrs-recipes/pom.xml index bad84cf2f..0aeb5c0d8 100644 --- a/components/jaxrs-recipes/pom.xml +++ b/components/jaxrs-recipes/pom.xml @@ -213,21 +213,38 @@ - - - - - - - - - - - - - - - - - + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + integration + methods + 10 + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.1.2 + + integration + + **/*Test.java + + false + + + + + integration-test + verify + + + + + + \ No newline at end of file diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java index 5c45f85f2..0a5bed761 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java @@ -20,12 +20,22 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.SourceFile; import org.openrewrite.config.Environment; import org.openrewrite.java.Assertions; import org.openrewrite.java.JavaParser; +import org.openrewrite.maven.MavenDownloadingExceptions; +import org.openrewrite.maven.MavenParser; +import org.openrewrite.maven.cache.LocalMavenArtifactCache; +import org.openrewrite.maven.internal.MavenPomDownloader; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.maven.utilities.MavenArtifactDownloader; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.xml.tree.Xml; import org.springframework.rewrite.plugin.polyglot.RewritePlugin; import org.springframework.rewrite.plugin.shared.PluginInvocationResult; import org.springframework.sbm.project.resource.TestProjectContext; @@ -33,7 +43,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.List; import static org.openrewrite.java.Assertions.mavenProject; @@ -47,45 +59,96 @@ public class JaxRsThroughAdapterTest { @Nested class WithRewriteTest implements RewriteTest { + public static final String POM_XML = """ + + + 4.0.0 + org.springframework.sbm.examples + migrate-jax-rs + jar + 0.0.1-SNAPSHOT + + UTF-8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + + + org.jboss.spec.javax.ws.rs + jboss-jaxrs-api_2.1_spec + 1.0.1.Final + + + + """; // FIXME: Use Openrewrite to retrieve dependency Paths that must be on classpath // mvn dependency:build-classpath -Dmdep.outputFile=cp.txt List deps = Arrays.stream("/Users/fkrueger/.m2/repository/org/jboss/spec/javax/ws/rs/jboss-jaxrs-api_2.1_spec/1.0.1.Final/jboss-jaxrs-api_2.1_spec-1.0.1.Final.jar".split(":")).map(d -> Path.of(d)).toList(); - private JavaParser.Builder javaParserBuilder = JavaParser.fromJavaVersion().classpath(deps); + private JavaParser.Builder javaParserBuilder = JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true).classpath(deps); @Override public void defaults(RecipeSpec spec) { + List classpath = getDependencyJarsForClasspath(POM_XML); + javaParserBuilder.classpath(classpath); spec.parser(javaParserBuilder); String RECIPE = "example.recipe.SbmAdapterRecipe"; + // Retrieve types + spec.recipe(Environment.builder() .scanRuntimeClasspath("example.recipe") .build() .activateRecipes(RECIPE)); } - @Test - public void theTest() { + public static List getDependencyJarsForClasspath(String pom) { + try { + Xml.Document doc = (Xml.Document) MavenParser.builder().build().parse(pom).toList().get(0); + MavenResolutionResult resolutionResult = doc.getMarkers().findFirst(MavenResolutionResult.class).orElseThrow(); + resolutionResult = resolutionResult.resolveDependencies(new MavenPomDownloader(Collections.emptyMap(), new InMemoryExecutionContext(), null, null), new InMemoryExecutionContext()); + List resolvedDependencies = resolutionResult.getDependencies().get(Scope.Compile); + MavenArtifactDownloader downloader = new MavenArtifactDownloader(new LocalMavenArtifactCache(Paths.get(System.getProperty("user.home"), ".m2", "repository")), null, (t) -> { + }); + return resolvedDependencies.stream().filter(d -> "jar".equals(d.getType())).map(downloader::downloadArtifact).toList(); + } catch (MavenDownloadingExceptions e) { + throw new RuntimeException(e); + } + } + @Test + public void simple() { rewriteRun( - (spec) -> spec.expectedCyclesThatMakeChanges(2), +// (spec) -> spec.expectedCyclesThatMakeChanges(2), mavenProject("project", Assertions.java( //language=Java """ package com.example.jee.app; - + import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.stream.Collectors; - + import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.Response.Status.Family.SUCCESSFUL; - + @Path("/") @Produces("application/json") public class PersonController { - + @POST @Path("/json/{name}") @Consumes("application/json") @@ -93,126 +156,143 @@ public String getHelloWorldJSON(@PathParam("name") String name) throws Exception System.out.println("name: " + name); return "{\\"Hello\\":\\"" + name + "\\""; } - - @GET - @Path("/json") - @Produces(APPLICATION_JSON) - @Consumes(APPLICATION_JSON) - public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { - return "{\\"message\\":\\"No person here...\\""; - } - - - @POST - @Path("/xml/{name}") - @Produces(MediaType.APPLICATION_XML) - @Consumes(MediaType.APPLICATION_XML) - public String getHelloWorldXML(@PathParam("name") String name) throws Exception { - System.out.println("name: " + name); - return "Hello "+name+""; - } - - private boolean isResponseStatusSuccessful(Response.Status.Family family) { - return family == SUCCESSFUL; - } - } """, //language=Java """ - package com.example.jee.app; - import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; - - import javax.ws.rs.DefaultValue; + import javax.ws.rs.PathParam; - import javax.ws.rs.QueryParam; + + + @RestController + @RequestMapping(value = "/", produces = "application/json") + public class PersonController { + + @RequestMapping(value = "/json/{name}", consumes = "application/json", method = RequestMethod.POST) + public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "{\\"Hello\\":\\"" + name + "\\""; + } + } + """ + ), + pomXml( + //language=xml + POM_XML + ) + ) + ); + } + + @Test + public void theTest() { + + rewriteRun( + (spec) -> spec.expectedCyclesThatMakeChanges(2), + mavenProject("project", + Assertions.java( + //language=Java + """ + package com.example.jee.app; + + import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - + import java.util.stream.Collectors; + import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.Response.Status.Family.SUCCESSFUL; - - - @RestController - @RequestMapping(value = "/", produces = "application/json") + + @Path("/") + @Produces("application/json") public class PersonController { - - @RequestMapping(value = "/json/{name}", consumes = "application/json", method = RequestMethod.POST) + + @POST + @Path("/json/{name}") + @Consumes("application/json") public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { System.out.println("name: " + name); return "{\\"Hello\\":\\"" + name + "\\""; } - - @RequestMapping(value = "/json", produces = APPLICATION_JSON, consumes = APPLICATION_JSON, method = RequestMethod.GET) + + @GET + @Path("/json") + @Produces(APPLICATION_JSON) + @Consumes(APPLICATION_JSON) public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { return "{\\"message\\":\\"No person here...\\""; } - - - @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) + + + @POST + @Path("/xml/{name}") + @Produces(MediaType.APPLICATION_XML) + @Consumes(MediaType.APPLICATION_XML) public String getHelloWorldXML(@PathParam("name") String name) throws Exception { System.out.println("name: " + name); return "Hello "+name+""; } - + private boolean isResponseStatusSuccessful(Response.Status.Family family) { return family == SUCCESSFUL; } - + } """ +// , + //language=Java +// """ +// package com.example.jee.app; +// +// import org.springframework.web.bind.annotation.RequestMapping; +// import org.springframework.web.bind.annotation.RequestMethod; +// import org.springframework.web.bind.annotation.RestController; +// +// import javax.ws.rs.DefaultValue; +// import javax.ws.rs.PathParam; +// import javax.ws.rs.QueryParam; +// import javax.ws.rs.core.MediaType; +// import javax.ws.rs.core.Response; +// +// import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +// import javax.ws.rs.core.Response.Status.Family; +// +// +// @RestController +// @RequestMapping(value = "/", produces = "application/json") +// public class PersonController { +// +// @RequestMapping(value = "/json/{name}", consumes = "application/json", method = RequestMethod.POST) +// public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { +// System.out.println("name: " + name); +// return "{\\"Hello\\":\\"" + name + "\\""; +// } +// +// @RequestMapping(value = "/json", produces = APPLICATION_JSON, consumes = APPLICATION_JSON, method = RequestMethod.GET) +// public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { +// return "{\\"message\\":\\"No person here...\\""; +// } +// +// +// @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) +// public String getHelloWorldXML(@PathParam("name") String name) throws Exception { +// System.out.println("name: " + name); +// return "Hello "+name+""; +// } +// +// private boolean isResponseStatusSuccessful(Response.Status.Family family) { +// return family == Family.SUCCESSFUL; +// } +// +// } +// """ ), pomXml( //language=xml - """ - - - 4.0.0 - org.springframework.sbm.examples - migrate-jax-rs - jar - 0.0.1-SNAPSHOT - - UTF-8 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - - - - - - - org.jboss.spec.javax.ws.rs - jboss-jaxrs-api_2.1_spec - 1.0.1.Final - - - - - jcenter - jcenter - https://jcenter.bintray.com - - - mavencentral - mavencentral - https://repo.maven.apache.org/maven2 - - - - """ + POM_XML ) ) ); From a1ab49328115c6b55d1e47509e1c156f6283581e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 17:52:57 +0200 Subject: [PATCH 15/19] Fixing more test --- .../sbm/JaxRsThroughAdapterTest.java | 94 ++-- .../MigrateEjbDeploymentDescriptorTest.java | 60 +-- ...MigrateLocalStatelessSessionBeansTest.java | 1 - .../actions/ConvertJaxRsAnnotationsTest.java | 273 +++++++----- .../actions/MigrateJaxRsAnnotations_Test.java | 125 ++++++ .../jee/jaxrs/recipes/CacheControlTest.java | 4 +- .../recipes/CopyAnnotationAttributeTest.java | 4 +- .../RemoveAnnotationIfAccompaniedTest.java | 4 +- .../jaxrs/recipes/ReplaceMediaTypeTest.java | 15 +- .../jaxrs/recipes/ResponseBuilderTest.java | 89 ++-- .../ResponseEntityReplacementTest.java | 404 +++++++++--------- .../recipes/ResponseStatusFamilyTest.java | 4 +- .../jee/jaxrs/recipes/ResponseStatusTest.java | 8 +- .../jaxrs/recipes/SwapHttpHeadersTest.java | 4 +- ...placeMdbAnnotationWithJmsListenerTest.java | 63 +-- ...JeeTransactionsToSpringBootActionTest.java | 93 ++-- .../sbm/test/RecipeTestSupport.java | 2 + 17 files changed, 716 insertions(+), 531 deletions(-) create mode 100644 components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.java diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java index 0a5bed761..55d54fcd7 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java @@ -130,7 +130,7 @@ public static List getDependencyJarsForClasspath(String pom) { @Test public void simple() { rewriteRun( -// (spec) -> spec.expectedCyclesThatMakeChanges(2), + (spec) -> spec.expectedCyclesThatMakeChanges(1), mavenProject("project", Assertions.java( //language=Java @@ -242,53 +242,53 @@ private boolean isResponseStatusSuccessful(Response.Status.Family family) { } """ -// , + , //language=Java -// """ -// package com.example.jee.app; -// -// import org.springframework.web.bind.annotation.RequestMapping; -// import org.springframework.web.bind.annotation.RequestMethod; -// import org.springframework.web.bind.annotation.RestController; -// -// import javax.ws.rs.DefaultValue; -// import javax.ws.rs.PathParam; -// import javax.ws.rs.QueryParam; -// import javax.ws.rs.core.MediaType; -// import javax.ws.rs.core.Response; -// -// import static javax.ws.rs.core.MediaType.APPLICATION_JSON; -// import javax.ws.rs.core.Response.Status.Family; -// -// -// @RestController -// @RequestMapping(value = "/", produces = "application/json") -// public class PersonController { -// -// @RequestMapping(value = "/json/{name}", consumes = "application/json", method = RequestMethod.POST) -// public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { -// System.out.println("name: " + name); -// return "{\\"Hello\\":\\"" + name + "\\""; -// } -// -// @RequestMapping(value = "/json", produces = APPLICATION_JSON, consumes = APPLICATION_JSON, method = RequestMethod.GET) -// public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { -// return "{\\"message\\":\\"No person here...\\""; -// } -// -// -// @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) -// public String getHelloWorldXML(@PathParam("name") String name) throws Exception { -// System.out.println("name: " + name); -// return "Hello "+name+""; -// } -// -// private boolean isResponseStatusSuccessful(Response.Status.Family family) { -// return family == Family.SUCCESSFUL; -// } -// -// } -// """ + """ + package com.example.jee.app; + + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; + + import javax.ws.rs.DefaultValue; + import javax.ws.rs.PathParam; + import javax.ws.rs.QueryParam; + import javax.ws.rs.core.MediaType; + import javax.ws.rs.core.Response; + + import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + import javax.ws.rs.core.Response.Status.Family; + + + @RestController + @RequestMapping(value = "/", produces = "application/json") + public class PersonController { + + @RequestMapping(value = "/json/{name}", consumes = "application/json", method = RequestMethod.POST) + public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "{\\"Hello\\":\\"" + name + "\\""; + } + + @RequestMapping(value = "/json", produces = APPLICATION_JSON, consumes = APPLICATION_JSON, method = RequestMethod.GET) + public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { + return "{\\"message\\":\\"No person here...\\""; + } + + + @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) + public String getHelloWorldXML(@PathParam("name") String name) throws Exception { + System.out.println("name: " + name); + return "Hello "+name+""; + } + + private boolean isResponseStatusSuccessful(Response.Status.Family family) { + return family == Family.SUCCESSFUL; + } + + } + """ ), pomXml( //language=xml diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java index 67c2d0bab..dffb7e001 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java @@ -145,32 +145,38 @@ void givenDeploymentDescriptorContainsEjbWithMappedName_whenMatchingClassIsFound void givenDeploymentDescriptorContainsEjbWithRemoteInterface_whenMatchingClassIsFound_thenStatelessRemoteAnnotationShouldBeGenerated() { // setup fixture String javaSource = - "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + - "import javax.ejb.Stateless;\n" + - "public class RemoteInterfaceView implements RemoteInterface{}"; - - String expected = "package com.example.jee.ejb.stateless.local.deploymentdescriptor;\n" + - "import javax.ejb.Remote;\n" + - "import javax.ejb.Stateless;\n" + - "\n" + - "@Stateless(name = \"" + EJB_WITH_REMOTE_INTERFACE_NAME + "\")\n" + - "@Remote(" + REMOTE_EJB_INTERFACE + ".class)\n" + - "public class RemoteInterfaceView implements RemoteInterface {}"; - - String deploymentDescriptorXml = "\n" + - " \n" + - " \n" + - " " + EJB_WITH_REMOTE_INTERFACE_NAME + "\n" + - " " + EJB_WITH_REMOTE_INTERFACE_FQDN + "\n" + - " " + REMOTE_EJB_INTERFACE + "\n" + - " " + EJB_TYPE + "\n" + - " \n" + - " \n" + - ""; + """ + package com.example.jee.ejb.stateless.local.deploymentdescriptor; + import javax.ejb.Stateless; + public class RemoteInterfaceView implements RemoteInterface{} + """; + + String expected = """ + package com.example.jee.ejb.stateless.local.deploymentdescriptor; + import javax.ejb.Remote; + import javax.ejb.Stateless; + + @Remote(com.example.jee.ejb.stateless.local.deploymentdescriptor.RemoteInterface.class) + @Stateless(name = "RemoteInterfaceView") + public class RemoteInterfaceView implements RemoteInterface {} + """; + + String deploymentDescriptorXml = """ + + + + RemoteInterfaceView + com.example.jee.ejb.stateless.local.deploymentdescriptor.RemoteInterfaceView + com.example.jee.ejb.stateless.local.deploymentdescriptor.RemoteInterface + Stateless + + + + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withProjectResource(Path.of("./src/main/resources/META-INF/ejb-jar.xml"), deploymentDescriptorXml) @@ -203,8 +209,8 @@ void givenDeploymentDescriptorContainsEjbWithLocalInterface_whenMatchingClassIsF "import javax.ejb.Local;\n" + "import javax.ejb.Stateless;\n" + "\n" + - "@Stateless(name = \"" + EJB_WITH_LOCAL_INTERFACE_NAME + "\")\n" + "@Local(" + LOCAL_EJB_INTERFACE + ".class)\n" + + "@Stateless(name = \"" + EJB_WITH_LOCAL_INTERFACE_NAME + "\")\n" + "public class LocalInterfaceView implements LocalInterface {}"; String deploymentDescriptorXml = " getMovies(@QueryParam("first") Integer first, @QueryParam("ma } """; + ProjectContext context = TestProjectContext + .buildProjectContext() + .withJavaSources(restControllerCode) + .withBuildFileHavingDependencies("org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:" + SPRING_VERSION) + .build(); + + Method jaxRsMethod = context.getProjectJavaSources() + .list() + .get(0) + .getTypes() + .get(0) + .getMethods() + .get(0); + + ConvertJaxRsAnnotations convertJaxRsAnnotations = ConvertJaxRsAnnotations + .builder() + .condition(HasTypeAnnotation.builder().annotation("javax.ws.rs.Path").build()) + .description("Convert JAX-RS annotations into Spring Boot annotations.") + .build(); + + convertJaxRsAnnotations.convertJaxRsMethodToSpringMvc(jaxRsMethod); + + @Language("java") - String expected = """ + String expected = + """ package com.example.jeerest.rest; - + import com.example.jeerest.Movie; import com.example.jeerest.MoviesBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; - import org.springframework.web.bind.annotation.RestController; - + import javax.ws.rs.DELETE; import javax.ws.rs.PUT; + import javax.ws.rs.Path; import javax.ws.rs.PathParam; + import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import java.util.List; - - - @RestController - @RequestMapping(value = "movies", produces = {"application/json"}) + + @Path("movies") + @Produces({"application/json"}) public class MoviesRest { - @RequestMapping(method = RequestMethod.GET) - public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, - @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { - return service.getMovies(first, max, field, searchTerm); - } + @RequestMapping(method = RequestMethod.GET) + public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, + @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { + return service.getMovies(first, max, field, searchTerm); + } } """; - - - ProjectContext projectContext = TestProjectContext.buildProjectContext() - .withJavaSources(restControllerCode) - .withBuildFileHavingDependencies("org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", "org.springframework.boot:spring-boot-starter-web:2.4.2") - .build(); - ConvertJaxRsAnnotations sut = new ConvertJaxRsAnnotations(); - sut.apply(projectContext); - - assertThat(projectContext.getProjectJavaSources().list().get(0).print()).isEqualTo(expected); +// """ +// package com.example.jeerest.rest; +// +// import com.example.jeerest.Movie; +// import com.example.jeerest.MoviesBean; +// import org.springframework.beans.factory.annotation.Autowired; +// +// import javax.ws.rs.DELETE; +// import javax.ws.rs.PUT; +// import javax.ws.rs.Path; +// import javax.ws.rs.PathParam; +// import javax.ws.rs.Produces; +// import javax.ws.rs.QueryParam; +// import javax.ws.rs.core.MediaType; +// import java.util.List; +// +// @Path("movies") +// @Produces({"application/json"}) +// public class MoviesRest { +// @RequestMapping(method = RequestMethod.GET) +// public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, +// @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { +// return service.getMovies(first, max, field, searchTerm); +// } +// } +// """; + + String given = context.getProjectJavaSources().list().get(0).print(); + assertThat(given).as(() -> TestDiff.of(given, expected)).isEqualTo(expected); } @Test - void replaceMethodAnnotationsWithOneAnnotation() { + void replaceMethodAnnotationsWithOneAnnotation() throws Exception { String sourceCode = """ import javax.ws.rs.Path; @@ -108,18 +159,21 @@ void replaceMethodAnnotationsWithOneAnnotation() { import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; - - + + @Path("/hello") class ControllerClass { @POST @Path("/json/{name}") @Produces({"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML}) @Consumes("application/json") - public String getHelloWorldJSON( @PathParam("name") String name) { + public String getHelloWorldJSON( + @PathParam("name")\s + String name) { return "Hello"; } - }"""; + } + """; String expected = """ import org.springframework.web.bind.annotation.RequestMapping; @@ -127,16 +181,19 @@ public String getHelloWorldJSON( @PathParam("name") String name) import org.springframework.web.bind.annotation.RestController; import javax.ws.rs.PathParam; import javax.ws.rs.core.MediaType; - - + + @RestController @RequestMapping(value = "/hello") class ControllerClass { @RequestMapping(value = "/json/{name}", produces = {"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML}, consumes = "application/json", method = RequestMethod.POST) - public String getHelloWorldJSON( @PathParam("name") String name) { + public String getHelloWorldJSON( + @PathParam("name")\s + String name) { return "Hello"; } - }"""; + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withJavaSources(sourceCode) @@ -153,36 +210,37 @@ public String getHelloWorldJSON( @PathParam("name") String name) @Test - void replaceMethodAnnotations() { - String sourceCode = """ - import javax.ws.rs.Path; - import javax.ws.rs.Consumes; - import javax.ws.rs.*; - import javax.ws.rs.Path; - import javax.ws.rs.PathParam; - import javax.ws.rs.Produces; - import javax.ws.rs.core.MediaType; - - - @Path("/hello") - class ControllerClass { - @POST - @GET - @PUT - @DELETE - @Path("/json/{name}") - @Produces({"image/jpeg", "image/gif", "image/png", MediaType.APPLICATION_XML}) - @Consumes("application/json") - public String getHelloWorldJSON(@PathParam("name") String name) { - return "Hello"; - } - public String notAnEndpoint(@PathParam("name") String name) { - return "Hello"; - } - }"""; + void replaceMethodAnnotations() throws Exception { + String sourceCode = + "import javax.ws.rs.Path;\n" + + "import javax.ws.rs.Consumes;\n" + + "import javax.ws.rs.*;\n" + + "import javax.ws.rs.Path;\n" + + "import javax.ws.rs.PathParam;\n" + + "import javax.ws.rs.Produces;\n" + + "import javax.ws.rs.core.MediaType;\n" + + " \n" + + " \n" + + "@Path(\"/hello\") \n" + + "class ControllerClass { \n" + + " @POST\n" + + " @GET\n" + + " @PUT\n" + + " @DELETE\n" + + " @Path(\"/json/{name}\")\n" + + " @Produces({\"image/jpeg\", \"image/gif\", \"image/png\", MediaType.APPLICATION_XML})\n" + + " @Consumes(\"application/json\")\n" + + " public String getHelloWorldJSON(@PathParam(\"name\") String name) {\n" + + " return \"Hello\";\n" + + " }\n" + + " public String notAnEndpoint(@PathParam(\"name\") String name) {\n" + + " return \"Hello\";\n" + + " }\n" + + "}"; @Language("java") - String expected = """ + String expected = + """ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -219,15 +277,15 @@ public String notAnEndpoint(@PathParam("name") String name) { void migrateToController() { String sourceCode = """ package com.example.jee.app; - + import javax.inject.Inject; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import java.util.stream.Collectors; - + @Path("/") public class PersonController { - + @POST @Path("/json/{name}") @Produces("application/json") @@ -235,7 +293,7 @@ public class PersonController { public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { return ""; } - + @GET @Path("/json") @Produces(MediaType.APPLICATION_JSON) @@ -243,7 +301,7 @@ public String getHelloWorldJSON(@PathParam("name") String name) throws Exception public String getAllPersons() throws Exception { return ""; } - + @POST @Path("/xml/{name}") @Produces(MediaType.APPLICATION_XML) @@ -251,37 +309,38 @@ public String getAllPersons() throws Exception { public String getHelloWorldXML(@PathParam("name") String name) throws Exception { return ""; } - }"""; + } + """; String expected = """ package com.example.jee.app; - + + import javax.ws.rs.PathParam; + import javax.ws.rs.core.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; - - import javax.ws.rs.PathParam; - import javax.ws.rs.core.MediaType; - + @RestController @RequestMapping(value = "/") public class PersonController { - + @RequestMapping(value = "/json/{name}", produces = "application/json", consumes = "application/json", method = RequestMethod.POST) public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { return ""; } - + @RequestMapping(value = "/json", produces = MediaType.APPLICATION_JSON, consumes = MediaType.APPLICATION_JSON, method = RequestMethod.GET) public String getAllPersons() throws Exception { return ""; } - + @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) public String getHelloWorldXML(@PathParam("name") String name) throws Exception { return ""; } - }"""; + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withJavaSources(sourceCode) @@ -296,37 +355,37 @@ public String getHelloWorldXML(@PathParam("name") String name) throws Exception } @Test - void addRequestBodyOverParameter() { - String sourceCode = """ - import javax.ws.rs.Path; - import javax.ws.rs.POST; - import javax.ws.rs.Path; - import javax.ws.rs.PathParam; - - @Path("/hello") - class ControllerClass { - @POST - @Path("/json/{name}") - public String create(@PathParam("name") String name, String data) { - return "Hello"; - } - }"""; - - String expected = """ - import org.springframework.web.bind.annotation.RequestBody; - import org.springframework.web.bind.annotation.RequestMapping; - import org.springframework.web.bind.annotation.RequestMethod; - import org.springframework.web.bind.annotation.RestController; - import javax.ws.rs.PathParam; - - @RestController - @RequestMapping(value = "/hello") - class ControllerClass { - @RequestMapping(value = "/json/{name}", method = RequestMethod.POST) - public String create(@PathParam("name") String name, @RequestBody String data) { - return "Hello"; - } - }"""; + void addRequestBodyOverParameter() throws Exception { + String sourceCode = + "import javax.ws.rs.Path;\n" + + "import javax.ws.rs.POST;\n" + + "import javax.ws.rs.Path;\n" + + "import javax.ws.rs.PathParam;\n" + + "\n" + + "@Path(\"/hello\")\n" + + "class ControllerClass {\n" + + " @POST\n" + + " @Path(\"/json/{name}\")\n" + + " public String create(@PathParam(\"name\") String name, String data) {\n" + + " return \"Hello\";\n" + + " }\n" + + "}"; + + String expected = + "import org.springframework.web.bind.annotation.RequestBody;\n" + + "import org.springframework.web.bind.annotation.RequestMapping;\n" + + "import org.springframework.web.bind.annotation.RequestMethod;\n" + + "import org.springframework.web.bind.annotation.RestController;\n" + + "import javax.ws.rs.PathParam;\n" + + "\n" + + "@RestController\n" + + "@RequestMapping(value = \"/hello\")\n" + + "class ControllerClass {\n" + + " @RequestMapping(value = \"/json/{name}\", method = RequestMethod.POST)\n" + + " public String create(@PathParam(\"name\") String name, @RequestBody String data) {\n" + + " return \"Hello\";\n" + + " }\n" + + "}"; ProjectContext projectContext = TestProjectContext.buildProjectContext() diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.java new file mode 100644 index 000000000..a96e00b67 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.java @@ -0,0 +1,125 @@ +/* + * Copyright 2021 - 2023 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.actions; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.api.Method; +import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation; +import org.springframework.sbm.project.resource.TestProjectContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Fabian Krüger + */ +public class MigrateJaxRsAnnotations_Test { + + private final static String SPRING_VERSION = "5.3.13"; + + @Test + void convertJaxRsMethodToSpringMvc() { + @Language("java") + String restControllerCode = """ + package com.example.jeerest.rest; + + import com.example.jeerest.Movie; + import com.example.jeerest.MoviesBean; + import org.springframework.beans.factory.annotation.Autowired; + + import javax.ws.rs.DELETE; + import javax.ws.rs.GET; + import javax.ws.rs.PUT; + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + import javax.ws.rs.Produces; + import javax.ws.rs.QueryParam; + import javax.ws.rs.core.MediaType; + import java.util.List; + + @Path("movies") + @Produces({"application/json"}) + public class MoviesRest { + @GET + public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, + @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { + return service.getMovies(first, max, field, searchTerm); + } + } + """; + + ProjectContext context = TestProjectContext + .buildProjectContext() + .withJavaSources(restControllerCode) + .withBuildFileHavingDependencies("org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:1.0.1.Final", + "org.springframework:spring-core:" + SPRING_VERSION) + .build(); + + Method jaxRsMethod = context.getProjectJavaSources() + .list() + .get(0) + .getTypes() + .get(0) + .getMethods() + .get(0); + + ConvertJaxRsAnnotations convertJaxRsAnnotations = ConvertJaxRsAnnotations + .builder() + .condition(HasTypeAnnotation.builder().annotation("javax.ws.rs.Path").build()) + .description("Convert JAX-RS annotations into Spring Boot annotations.") + .build(); + + convertJaxRsAnnotations.convertJaxRsMethodToSpringMvc(jaxRsMethod); + + + @Language("java") + String expected = + """ + package com.example.jeerest.rest; + + import com.example.jeerest.Movie; + import com.example.jeerest.MoviesBean; + import org.springframework.beans.factory.annotation.Autowired; + + import javax.ws.rs.DELETE; + import javax.ws.rs.PUT; + import javax.ws.rs.Path; + import javax.ws.rs.PathParam; + import javax.ws.rs.Produces; + import javax.ws.rs.QueryParam; + import javax.ws.rs.core.MediaType; + import java.util.List; + + @Path("movies") + @Produces({"application/json"}) + public class MoviesRest { + @RequestMapping(method = RequestMethod.GET) + public List getMovies(@QueryParam("first") Integer first, @QueryParam("max") Integer max, + @QueryParam("field") String field, @QueryParam("searchTerm") String searchTerm) { + return service.getMovies(first, max, field, searchTerm); + } + } + """; + + assertThat(context.getProjectJavaSources().list().get(0).print()).isEqualTo( + expected + ); + + + } + +} diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CacheControlTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CacheControlTest.java index 591218083..605199a01 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CacheControlTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CacheControlTest.java @@ -15,11 +15,11 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; -import org.springframework.sbm.engine.recipe.AbstractAction; +import org.junit.jupiter.api.Test; import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; -import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java index 87fe932ff..e3276147a 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/CopyAnnotationAttributeTest.java @@ -179,7 +179,7 @@ void givenThereAreOtherAnnotationsPresentThanTheSourceAndTargetAnnotation_thenTh String sourceCode = """ import org.springframework.web.bind.annotation.RequestParam; import javax.ws.rs.DefaultValue; - import javax.validation.constraints.NotNull; + import jakarta.validation.constraints.NotNull; class ControllerClass { public String test(@RequestParam(value = "q") @NotNull @DefaultValue("default-value") String searchString) { @@ -191,7 +191,7 @@ public String test(@RequestParam(value = "q") @NotNull @DefaultValue("default-va String expected = """ import org.springframework.web.bind.annotation.RequestParam; import javax.ws.rs.DefaultValue; - import javax.validation.constraints.NotNull; + import jakarta.validation.constraints.NotNull; class ControllerClass { public String test(@RequestParam(defaultValue = "default-value", value = "q") @NotNull @DefaultValue("default-value") String searchString) { diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java index 955fa105b..025f2dfc9 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/RemoveAnnotationIfAccompaniedTest.java @@ -81,7 +81,7 @@ public String test(@RequestParam(value = "q") String searchString) { void givenBothAnnotationsArePresentOnTheFirstMethodParameterAndPrecededByAnotherOne_thenTheAnnotationIsRemoved() { String sourceCode = """ import org.springframework.web.bind.annotation.RequestParam; - import javax.validation.constraints.NotNull; + import jakarta.validation.constraints.NotNull; import javax.ws.rs.DefaultValue; class ControllerClass { @@ -93,7 +93,7 @@ public String test(@NotNull @DefaultValue("default-value") @RequestParam(value = String expected = """ import org.springframework.web.bind.annotation.RequestParam; - import javax.validation.constraints.NotNull; + import jakarta.validation.constraints.NotNull; class ControllerClass { public String test(@NotNull @RequestParam(value = "q") String searchString) { diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java index abafef10c..c584cbda9 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java @@ -15,19 +15,12 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; -import org.openrewrite.java.JavaParser; -import org.springframework.rewrite.parser.RewriteExecutionContext; -import org.springframework.rewrite.parser.SpringRewriteProperties; +import org.junit.jupiter.api.Test; +import org.springframework.sbm.engine.context.ProjectContext; import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.java.api.JavaSource; -import org.springframework.sbm.engine.context.ProjectContext; -import org.springframework.sbm.java.impl.RewriteJavaParser; -import org.springframework.sbm.project.resource.SbmApplicationProperties; import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; -import org.junit.jupiter.api.Test; - -import java.util.function.Supplier; import static org.assertj.core.api.Assertions.assertThat; @@ -485,9 +478,10 @@ public MediaType respond() { """; String expected = """ - import java.util.Map; import org.springframework.http.MediaType; + import java.util.Map; + public class TestController { public MediaType respond() { @@ -536,6 +530,7 @@ public String getHelloWorldJSON(@PathParam("name") String name) { """ import javax.ws.rs.Path; import javax.ws.rs.PathParam; + import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java index 2cc83dbde..777864071 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java @@ -15,18 +15,17 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; -import org.openrewrite.java.tree.J; +import org.junit.jupiter.api.Test; +import org.openrewrite.SourceFile; import org.springframework.rewrite.parser.RewriteExecutionContext; import org.springframework.rewrite.parser.SpringRewriteProperties; -import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.java.impl.RewriteJavaParser; -import org.springframework.sbm.project.resource.SbmApplicationProperties; import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; -import org.junit.jupiter.api.Test; -import java.util.List; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -63,6 +62,7 @@ void allow() { String expected = "" + "import java.util.Set;\n" + + "\n" + "import org.springframework.http.HttpMethod;\n" + "import org.springframework.http.ResponseEntity;\n" + "\n" @@ -111,9 +111,10 @@ void expires() { + ""; String expected = "" - + "import java.util.Date;\n" + "import org.springframework.http.ResponseEntity;\n" + "\n" + + "import java.util.Date;\n" + + "\n" + "public class TestController {\n" + "\n" + " public ResponseEntity.BodyBuilder test() {\n" @@ -157,6 +158,7 @@ void language() { String expected = "" + "import java.util.Locale;\n" + + "\n" + "import org.springframework.http.HttpHeaders;\n" + "import org.springframework.http.ResponseEntity;\n" + "\n" @@ -204,9 +206,10 @@ void lastModified() { + ""; String expected = "" - + "import java.util.Date;\n" + "import org.springframework.http.ResponseEntity;\n" + "\n" + + "import java.util.Date;\n" + + "\n" + "public class TestController {\n" + "\n" + " public ResponseEntity.BodyBuilder test() {\n" @@ -233,38 +236,39 @@ void lastModified() { @Test void replaceAll() { - String javaSource = """ - import javax.ws.rs.core.MultivaluedMap; - import javax.ws.rs.core.Response.ResponseBuilder; - - public class TestController { - - public ResponseBuilder test() { - ResponseBuilder b; - MultivaluedMap m; - b.replaceAll(m); - return b; - } - } - """; - - String expected = """ - import javax.ws.rs.core.MultivaluedMap; - import org.springframework.http.ResponseEntity; - - public class TestController { - - public ResponseEntity.BodyBuilder test() { - ResponseEntity.BodyBuilder b; - MultivaluedMap m; - b.headers(h -> { - h.clear(); - h.addAll(m); - }); - return b; - } - } - """; + String javaSource = "" + + "import javax.ws.rs.core.MultivaluedMap;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder test() {\n" + + " ResponseBuilder b;\n" + + " MultivaluedMap m;\n" + + " b.replaceAll(m);\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import javax.ws.rs.core.MultivaluedMap;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity.BodyBuilder test() {\n" + + " ResponseEntity.BodyBuilder b;\n" + + " MultivaluedMap m;\n" + + " b.headers(h -> {\n" + + " h.clear();\n" + + " h.addAll(m);\n" + + " });\n" + + " return b;\n" + + " }\n" + + "}\n" + + ""; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( @@ -371,11 +375,8 @@ void type() { String actual = projectContext.getProjectJavaSources().list().get(0).print(); // verify it compiles - List parse = new RewriteJavaParser(new SpringRewriteProperties(), - new RewriteExecutionContext()) - .parse(actual) - .map(J.CompilationUnit.class::cast) - .toList(); + Stream parse = new RewriteJavaParser(new SpringRewriteProperties(), + new RewriteExecutionContext()).parse(actual); assertThat(actual) .as(TestDiff.of(actual, expected)) diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java index bc7333ad6..46a76181f 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -21,8 +21,6 @@ import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; -import java.util.stream.Collectors; - import static org.assertj.core.api.Assertions.assertThat; public class ResponseEntityReplacementTest { @@ -33,10 +31,8 @@ public class ResponseEntityReplacementTest { new AbstractAction() { @Override public void apply(ProjectContext context) { - context.getProjectJavaSources().apply( - new SwapResponseWithResponseEntity(), - new ReplaceMediaType() - ); + context.getProjectJavaSources().apply(new SwapResponseWithResponseEntity()); + context.getProjectJavaSources().apply(new ReplaceMediaType()); } }; @@ -87,25 +83,27 @@ void testUnsupportedStaticCall() { @Test void testUnsupportedBuilderCall() { - String javaSource = """ - import javax.ws.rs.core.Response; - - public class TestController { - public Response respond() { - return Response.status(200).tag("My Tag").build(); - } - } - """; - - String expected = """ - import org.springframework.http.ResponseEntity; - - public class TestController { - public ResponseEntity respond() { - return ResponseEntity.status(200).eTag("My Tag").build(); - } - } - """; + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.status(200).tag(\"My Tag\").build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.status(200).eTag(\"My Tag\").build();\n" + + " }\n" + + "}\n" + + ""; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( @@ -371,28 +369,28 @@ void testReplaceOkWithMediaTypeAndBody() { @Test void testReplaceOkWithMediaTypeStringAndBody() { - String javaSource = """ - import javax.ws.rs.core.Response; - - public class TestController { - - public Response respond() { - return Response.ok("All good!", "application/json").build(); - } - } - """; - - String expected = """ - import org.springframework.http.MediaType; - import org.springframework.http.ResponseEntity; - - public class TestController { - - public ResponseEntity respond() { - return ResponseEntity.ok().contentType(MediaType.parseMediaType("application/json")).body("All good!"); - } - } - """; + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.ok(\"All good!\", \"application/json\").build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.MediaType;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.ok().contentType(MediaType.parseMediaType(\"application/json\")).body(\"All good!\");\n" + + " }\n" + + "}\n" + + ""; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( @@ -511,6 +509,7 @@ void created() { String expected = "" + "import org.springframework.http.ResponseEntity;\n" + + "\n" + "import java.net.URI;\n" + "\n" + "public class TestController {\n" @@ -642,6 +641,7 @@ void seeOther() { String expected = "" + "import org.springframework.http.HttpStatus;\n" + "import org.springframework.http.ResponseEntity;\n" + + "\n" + "import java.net.URI;\n" + "\n" + "public class TestController {\n" @@ -714,32 +714,33 @@ void serverError() { @Test void temporaryRedirect() { - String javaSource = """ - import javax.ws.rs.core.Response; - import java.net.URI; - - public class TestController { - - public Response respond() { - URI uri = URI.create("https://spring.io"); - return Response.temporaryRedirect(uri).build(); - } - } - """; - - String expected = """ - import org.springframework.http.HttpStatus; - import org.springframework.http.ResponseEntity; - import java.net.URI; - - public class TestController { - - public ResponseEntity respond() { - URI uri = URI.create("https://spring.io"); - return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(uri).build(); - } - } - """; + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import java.net.URI;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " URI uri = URI.create(\"https://spring.io\");\n" + + " return Response.temporaryRedirect(uri).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.HttpStatus;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "import java.net.URI;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " URI uri = URI.create(\"https://spring.io\");\n" + + " return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).location(uri).build();\n" + + " }\n" + + "}\n" + + ""; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( @@ -759,87 +760,87 @@ public ResponseEntity respond() { @Test void instanceMethods() { - String javaSource = """ - import javax.ws.rs.core.Response; - import javax.ws.rs.core.GenericType; - import java.lang.annotation.Annotation; - - public class TestController { - - public String respond() { - Response r = Response.ok().build(); - r.getAllowedMethods(); - r.bufferEntity(); - r.close(); - r.getCookies(); - r.getDate(); - r.getEntity(); - r.bufferEntity(); - r.getEntityTag(); - r.getHeaders(); - r.getHeaderString("Accept"); - r.getLanguage(); - r.getLastModified(); - r.getLength(); - r.getLink("Something"); - r.getLinkBuilder("Something"); - r.getLinks(); - r.getLocation(); - r.getMediaType(); - r.getMetadata(); - r.getStatus(); - r.getStatusInfo(); - r.getStringHeaders(); - r.hasEntity(); - r.hasLink("Something"); - r.readEntity(String.class, new Annotation[0]); - r.readEntity(GenericType.forInstance("Something")); - r.readEntity(GenericType.forInstance("Something"), new Annotation[0]); - return r.readEntity(String.class); - } - } - """; - - String expected = """ - import org.springframework.http.ResponseEntity; - import java.util.Date; - import java.util.stream.Collectors; - - public class TestController { - - public String respond() { - ResponseEntity r = ResponseEntity.ok().build(); - r.getHeaders().getAllow().stream().map(m -> m.toString()).collect(Collectors.toList()); - r.bufferEntity(); - r.close(); - r.getCookies(); - new Date(r.getHeaders().getDate()); - r.getBody(); - r.bufferEntity(); - r.getHeaders().getETag(); - r.getHeaders(); - r.getHeaders().get("Accept").stream().collect(Collectors.joining(", ")); - r.getHeaders().getContentLanguage(); - new Date(r.getHeaders().getLastModified()); - r.getHeaders().getContentLength(); - r.getLink("Something"); - r.getLinkBuilder("Something"); - r.getLinks(); - r.getHeaders().getLocation(); - r.getHeaders().getContentType(); - r.getHeaders(); - r.getStatusCodeValue(); - r.getStatusCode(); - r.getHeaders(); - r.hasBody(); - r.hasLink("Something"); - r.getBody(); - r.getBody(); - r.getBody(); - return r.getBody(); - } - } - """; + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import javax.ws.rs.core.GenericType;\n" + + "import java.lang.annotation.Annotation;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public String respond() {\n" + + " Response r = Response.ok().build();\n" + + " r.getAllowedMethods();\n" + + " r.bufferEntity();\n" + + " r.close();\n" + + " r.getCookies();\n" + + " r.getDate();\n" + + " r.getEntity();\n" + + " r.bufferEntity();\n" + + " r.getEntityTag();\n" + + " r.getHeaders();\n" + + " r.getHeaderString(\"Accept\");\n" + + " r.getLanguage();\n" + + " r.getLastModified();\n" + + " r.getLength();\n" + + " r.getLink(\"Something\");\n" + + " r.getLinkBuilder(\"Something\");\n" + + " r.getLinks();\n" + + " r.getLocation();\n" + + " r.getMediaType();\n" + + " r.getMetadata();\n" + + " r.getStatus();\n" + + " r.getStatusInfo();\n" + + " r.getStringHeaders();\n" + + " r.hasEntity();\n" + + " r.hasLink(\"Something\");\n" + + " r.readEntity(String.class, new Annotation[0]);\n" + + " r.readEntity(GenericType.forInstance(\"Something\"));\n" + + " r.readEntity(GenericType.forInstance(\"Something\"), new Annotation[0]);\n" + + " return r.readEntity(String.class);\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.ResponseEntity;\n" + + "import java.util.Date;\n" + + "import java.util.stream.Collectors;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public String respond() {\n" + + " ResponseEntity r = ResponseEntity.ok().build();\n" + + " r.getHeaders().getAllow().stream().map(m -> m.toString()).collect(Collectors.toList());\n" + + " r.bufferEntity();\n" + + " r.close();\n" + + " r.getCookies();\n" + + " new Date(r.getHeaders().getDate());\n" + + " r.getBody();\n" + + " r.bufferEntity();\n" + + " r.getHeaders().getETag();\n" + + " r.getHeaders();\n" + + " r.getHeaders().get(\"Accept\").stream().collect(Collectors.joining(\", \"));\n" + + " r.getHeaders().getContentLanguage();\n" + + " new Date(r.getHeaders().getLastModified());\n" + + " r.getHeaders().getContentLength();\n" + + " r.getLink(\"Something\");\n" + + " r.getLinkBuilder(\"Something\");\n" + + " r.getLinks();\n" + + " r.getHeaders().getLocation();\n" + + " r.getHeaders().getContentType();\n" + + " r.getHeaders();\n" + + " r.getStatusCodeValue();\n" + + " r.getStatusCode();\n" + + " r.getHeaders();\n" + + " r.hasBody();\n" + + " r.hasLink(\"Something\");\n" + + " r.getBody();\n" + + " r.getBody();\n" + + " r.getBody();\n" + + " return r.getBody();\n" + + " }\n" + + "}\n" + + ""; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( @@ -859,30 +860,29 @@ public String respond() { @Test void chain_1() { - String javaSource = """ - import javax.ws.rs.core.Response; - import javax.ws.rs.core.MediaType; - - public class TestController { - - public Response respond() { - return Response.status(200).entity("Hello").tag("My Tag").type(MediaType.TEXT_PLAIN_TYPE).build(); - } - } - """; - - String expected = """ - import org.springframework.http.MediaType; - - import org.springframework.http.ResponseEntity; - - public class TestController { - - public ResponseEntity respond() { - return ResponseEntity.status(200).eTag("My Tag").contentType(MediaType.TEXT_PLAIN).body("Hello"); - } - } - """; + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import javax.ws.rs.core.MediaType;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public Response respond() {\n" + + " return Response.status(200).entity(\"Hello\").tag(\"My Tag\").type(MediaType.TEXT_PLAIN_TYPE).build();\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.MediaType;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.status(200).eTag(\"My Tag\").contentType(MediaType.TEXT_PLAIN).body(\"Hello\");\n" + + " }\n" + + "}\n" + + ""; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( @@ -902,30 +902,30 @@ public ResponseEntity respond() { @Test void chain_2() { - String javaSource = """ - import javax.ws.rs.core.Response; - import javax.ws.rs.core.Response.ResponseBuilder; - import javax.ws.rs.core.MediaType; - - public class TestController { - - public ResponseBuilder respond() { - return Response.status(200).entity("Hello").tag("My Tag").type(MediaType.TEXT_PLAIN_TYPE); - } - } - """; - - String expected = """ - import org.springframework.http.MediaType; - import org.springframework.http.ResponseEntity; - - public class TestController { - - public ResponseEntity respond() { - return ResponseEntity.status(200).eTag("My Tag").contentType(MediaType.TEXT_PLAIN).body("Hello"); - } - } - """; + String javaSource = "" + + "import javax.ws.rs.core.Response;\n" + + "import javax.ws.rs.core.Response.ResponseBuilder;\n" + + "import javax.ws.rs.core.MediaType;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseBuilder respond() {\n" + + " return Response.status(200).entity(\"Hello\").tag(\"My Tag\").type(MediaType.TEXT_PLAIN_TYPE);\n" + + " }\n" + + "}\n" + + ""; + + String expected = "" + + "import org.springframework.http.MediaType;\n" + + "import org.springframework.http.ResponseEntity;\n" + + "\n" + + "public class TestController {\n" + + "\n" + + " public ResponseEntity respond() {\n" + + " return ResponseEntity.status(200).eTag(\"My Tag\").contentType(MediaType.TEXT_PLAIN).body(\"Hello\");\n" + + " }\n" + + "}\n" + + ""; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusFamilyTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusFamilyTest.java index 891a29720..709bdaebf 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusFamilyTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusFamilyTest.java @@ -15,11 +15,11 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; -import org.springframework.sbm.engine.recipe.AbstractAction; +import org.junit.jupiter.api.Test; import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; -import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java index 942cecf69..df94b89c7 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java @@ -15,15 +15,11 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; -import org.springframework.rewrite.parser.RewriteExecutionContext; -import org.springframework.rewrite.parser.SpringRewriteProperties; -import org.springframework.sbm.engine.recipe.AbstractAction; +import org.junit.jupiter.api.Test; import org.springframework.sbm.engine.context.ProjectContext; -import org.springframework.sbm.java.impl.RewriteJavaParser; -import org.springframework.sbm.project.resource.SbmApplicationProperties; +import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; -import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttpHeadersTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttpHeadersTest.java index 7e65d9bbc..ef63f50b7 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttpHeadersTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttpHeadersTest.java @@ -15,11 +15,11 @@ */ package org.springframework.sbm.jee.jaxrs.recipes; -import org.springframework.sbm.engine.recipe.AbstractAction; +import org.junit.jupiter.api.Test; import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.AbstractAction; import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.testhelper.common.utils.TestDiff; -import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java index 54a445cb4..53e73cddc 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java @@ -25,39 +25,40 @@ public class ReplaceMdbAnnotationWithJmsListenerTest { void testReplaceMdbAnnotation() { String given = - "import javax.ejb.ActivationConfigProperty;\n" - + "import javax.ejb.MessageDriven;\n" - + "import javax.jms.Message;\n" - + "import javax.jms.MessageListener;\n" - + "\n" - + "@MessageDriven(activationConfig = {\n" - + " @ActivationConfigProperty(propertyName = \"destinationType\", \n" - + " propertyValue = \"javax.jms.Queue\"),\n" - + " @ActivationConfigProperty(propertyName = \"destinationLookup\", \n" - + " propertyValue = \"java:app/jms/CargoHandledQueue\")\n" - + "})\n" - + "public class CargoHandledConsumer implements MessageListener {\n" - + "\n" - + " @Override\n" - + " public void onMessage(Message message) {\n" - + " }\n" - + "}\n" - + ""; + """ + import javax.ejb.ActivationConfigProperty; + import javax.ejb.MessageDriven; + import javax.jms.Message; + import javax.jms.MessageListener; + + @MessageDriven(activationConfig = { + @ActivationConfigProperty(propertyName = "destinationType",\s + propertyValue = "javax.jms.Queue"), + @ActivationConfigProperty(propertyName = "destinationLookup",\s + propertyValue = "java:app/jms/CargoHandledQueue") + }) + public class CargoHandledConsumer implements MessageListener { + + @Override + public void onMessage(Message message) { + } + } + """; String expected = - "import org.springframework.jms.annotation.JmsListener;\n" - + "import org.springframework.stereotype.Component;\n" - + "\n" - + "import javax.jms.Message;\n" - + "\n" - + "@Component\n" - + "public class CargoHandledConsumer {\n" - + "\n" - + " @JmsListener(destination = \"CargoHandledQueue\")\n" - + " public void onMessage(Message message) {\n" - + " }\n" - + "}\n" - + ""; + """ + import javax.jms.Message; + import org.springframework.jms.annotation.JmsListener; + import org.springframework.stereotype.Component; + + @Component + public class CargoHandledConsumer { + + @JmsListener(destination = "CargoHandledQueue") + public void onMessage(Message message) { + } + } + """; JavaMigrationActionTestSupport.verify(given, expected, new ReplaceMdbAnnotationWithJmsListener(), "javax:javaee-api:7.0", diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java index cae4079f2..b3bb045be 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java @@ -28,53 +28,54 @@ class MigrateJeeTransactionsToSpringBootActionTest { @Test void migrateTransactionAnnotations() { - String given = - "import javax.ejb.*;\n" + - "@TransactionManagement(TransactionManagementType.CONTAINER)\n" + - "@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)\n" + - "public class TransactionalService {\n" + - " public void requiresNewFromType() {}\n" + - " @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)\n" + - " public void notSupported() {}\n" + - " @TransactionAttribute(TransactionAttributeType.MANDATORY)\n" + - " public void mandatory() {}\n" + - " @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)\n" + - " public void requiresNew() {}\n" + - " @TransactionAttribute(TransactionAttributeType.REQUIRED)\n" + - " public void required() {}\n" + - " @TransactionAttribute(TransactionAttributeType.NEVER)\n" + - " public void never() {}\n" + - " @TransactionAttribute(TransactionAttributeType.SUPPORTS)\n" + - " public void supports() {}\n" + - "}"; + String given = """ + import javax.ejb.*; + @TransactionManagement(TransactionManagementType.CONTAINER) + @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) + public class TransactionalService { + public void requiresNewFromType() {} + @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) + public void notSupported() {} + @TransactionAttribute(TransactionAttributeType.MANDATORY) + public void mandatory() {} + @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) + public void requiresNew() {} + @TransactionAttribute(TransactionAttributeType.REQUIRED) + public void required() {} + @TransactionAttribute(TransactionAttributeType.NEVER) + public void never() {} + @TransactionAttribute(TransactionAttributeType.SUPPORTS) + public void supports() {} + } + """; - String expected = - "import org.springframework.transaction.annotation.Propagation;\n" + - "import org.springframework.transaction.annotation.Transactional;\n" + - "\n" + - "\n" + - "@Transactional(propagation = Propagation.REQUIRES_NEW)\n" + - "public class TransactionalService {\n" + - " public void requiresNewFromType() {}\n" + - "\n" + - " @Transactional(propagation = Propagation.NOT_SUPPORTED)\n" + - " public void notSupported() {}\n" + - "\n" + - " @Transactional(propagation = Propagation.MANDATORY)\n" + - " public void mandatory() {}\n" + - "\n" + - " @Transactional(propagation = Propagation.REQUIRES_NEW)\n" + - " public void requiresNew() {}\n" + - "\n" + - " @Transactional(propagation = Propagation.REQUIRED)\n" + - " public void required() {}\n" + - "\n" + - " @Transactional(propagation = Propagation.NEVER)\n" + - " public void never() {}\n" + - "\n" + - " @Transactional(propagation = Propagation.SUPPORTS)\n" + - " public void supports() {}\n" + - "}"; + String expected = """ + import org.springframework.transaction.annotation.Propagation; + import org.springframework.transaction.annotation.Transactional; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public class TransactionalService { + public void requiresNewFromType() {} + + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public void notSupported() {} + + @Transactional(propagation = Propagation.MANDATORY) + public void mandatory() {} + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void requiresNew() {} + + @Transactional(propagation = Propagation.REQUIRED) + public void required() {} + + @Transactional(propagation = Propagation.NEVER) + public void never() {} + + @Transactional(propagation = Propagation.SUPPORTS) + public void supports() {} + } + """; MigrateJeeTransactionsToSpringBootAction sut = new MigrateJeeTransactionsToSpringBootAction(); diff --git a/components/recipe-test-support/src/main/java/org/springframework/sbm/test/RecipeTestSupport.java b/components/recipe-test-support/src/main/java/org/springframework/sbm/test/RecipeTestSupport.java index bbeac64a7..a878e3b43 100644 --- a/components/recipe-test-support/src/main/java/org/springframework/sbm/test/RecipeTestSupport.java +++ b/components/recipe-test-support/src/main/java/org/springframework/sbm/test/RecipeTestSupport.java @@ -23,6 +23,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.rewrite.boot.autoconfigure.ScopeConfiguration; import org.springframework.rewrite.parser.maven.MavenSettingsInitializer; +import org.springframework.rewrite.resource.RewriteMigrationResultMerger; import org.springframework.rewrite.resource.RewriteSourceFileWrapper; import org.springframework.sbm.build.impl.MavenBuildFileRefactoringFactory; import org.springframework.sbm.build.impl.RewriteMavenParser; @@ -66,6 +67,7 @@ private RecipeTestSupport() { ResourceHelperDummy.class, ActionDeserializerRegistry.class, MultiModuleAwareActionDeserializer.class, + RewriteMigrationResultMerger.class, DefaultActionDeserializer.class, RewriteJavaSearchActionDeserializer.class, RewriteRecipeLoader.class, From 0683296a7d4fe2570a79e5bcbcb65eca20cfb679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 17:57:23 +0200 Subject: [PATCH 16/19] Existing jaxrs tests are green in jaxrs module --- .../jaxrs/recipes/ResponseBuilderTest.java | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java index 777864071..789320aac 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java @@ -191,34 +191,33 @@ void language() { @Test void lastModified() { - String javaSource = "" - + "import java.util.Date;\n" - + "import javax.ws.rs.core.Response.ResponseBuilder;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseBuilder test() {\n" - + " ResponseBuilder b;\n" - + " b.lastModified(new Date(100000));\n" - + " return b;\n" - + " }\n" - + "}\n" - + ""; - - String expected = "" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "import java.util.Date;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity.BodyBuilder test() {\n" - + " ResponseEntity.BodyBuilder b;\n" - + " b.lastModified(new Date(100000).toInstant());\n" - + " return b;\n" - + " }\n" - + "}\n" - + ""; + String javaSource = """ + import java.util.Date; + import javax.ws.rs.core.Response.ResponseBuilder; + + public class TestController { + + public ResponseBuilder test() { + ResponseBuilder b; + b.lastModified(new Date(100000)); + return b; + } + } + """; + + String expected = """ + import java.util.Date; + import org.springframework.http.ResponseEntity; + + public class TestController { + + public ResponseEntity.BodyBuilder test() { + ResponseEntity.BodyBuilder b; + b.lastModified(new Date(100000).toInstant()); + return b; + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies("javax:javaee-api:8.0") From 54db42d1132fc5e2f2383596920a1c8b3145d7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 5 Aug 2024 22:07:50 +0200 Subject: [PATCH 17/19] Fixing tests --- .../sbm/JaxRsThroughAdapterTest.java | 4 +- .../actions/MigrateJaxRsAnnotations_Test.java | 6 +- .../jaxrs/recipes/ReplaceMediaTypeTest.java | 4 +- .../jaxrs/recipes/ResponseBuilderTest.java | 59 +++++++++---------- .../ResponseEntityReplacementTest.java | 3 - 5 files changed, 36 insertions(+), 40 deletions(-) diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java index 55d54fcd7..afd61cea0 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java @@ -130,7 +130,7 @@ public static List getDependencyJarsForClasspath(String pom) { @Test public void simple() { rewriteRun( - (spec) -> spec.expectedCyclesThatMakeChanges(1), + (spec) -> spec.expectedCyclesThatMakeChanges(2), mavenProject("project", Assertions.java( //language=Java @@ -258,7 +258,7 @@ private boolean isResponseStatusSuccessful(Response.Status.Family family) { import javax.ws.rs.core.Response; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; - import javax.ws.rs.core.Response.Status.Family; + import static javax.ws.rs.core.Response.Status.Family; @RestController diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.java index a96e00b67..292899543 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.java @@ -40,6 +40,8 @@ void convertJaxRsMethodToSpringMvc() { import com.example.jeerest.Movie; import com.example.jeerest.MoviesBean; import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -94,7 +96,9 @@ public List getMovies(@QueryParam("first") Integer first, @QueryParam("ma import com.example.jeerest.Movie; import com.example.jeerest.MoviesBean; import org.springframework.beans.factory.annotation.Autowired; - + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + import javax.ws.rs.DELETE; import javax.ws.rs.PUT; import javax.ws.rs.Path; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java index c584cbda9..cd1ec54d6 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java @@ -478,9 +478,8 @@ public MediaType respond() { """; String expected = """ - import org.springframework.http.MediaType; - import java.util.Map; + import org.springframework.http.MediaType; public class TestController { @@ -530,7 +529,6 @@ public String getHelloWorldJSON(@PathParam("name") String name) { """ import javax.ws.rs.Path; import javax.ws.rs.PathParam; - import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java index 789320aac..1d15264bc 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java @@ -62,7 +62,6 @@ void allow() { String expected = "" + "import java.util.Set;\n" - + "\n" + "import org.springframework.http.HttpMethod;\n" + "import org.springframework.http.ResponseEntity;\n" + "\n" @@ -110,20 +109,19 @@ void expires() { + "}\n" + ""; - String expected = "" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "import java.util.Date;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity.BodyBuilder test() {\n" - + " ResponseEntity.BodyBuilder b;\n" - + " b.headers(h -> h.setExpires(new Date(100000).toInstant()));\n" - + " return b;\n" - + " }\n" - + "}\n" - + ""; + String expected = """ + import java.util.Date; + import org.springframework.http.ResponseEntity; + + public class TestController { + + public ResponseEntity.BodyBuilder test() { + ResponseEntity.BodyBuilder b; + b.headers(h -> h.setExpires(new Date(100000).toInstant())); + return b; + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies("javax:javaee-api:8.0") @@ -156,22 +154,21 @@ void language() { + "}\n" + ""; - String expected = "" - + "import java.util.Locale;\n" - + "\n" - + "import org.springframework.http.HttpHeaders;\n" - + "import org.springframework.http.ResponseEntity;\n" - + "\n" - + "public class TestController {\n" - + "\n" - + " public ResponseEntity.BodyBuilder test() {\n" - + " ResponseEntity.BodyBuilder b;\n" - + " b.headers(h -> h.set(HttpHeaders.CONTENT_LANGUAGE, \"ua\"));\n" - + " b.headers(h -> h.setContentLanguage(Locale.ITALY));\n" - + " return b;\n" - + " }\n" - + "}\n" - + ""; + String expected = """ + import java.util.Locale; + import org.springframework.http.HttpHeaders; + import org.springframework.http.ResponseEntity; + + public class TestController { + + public ResponseEntity.BodyBuilder test() { + ResponseEntity.BodyBuilder b; + b.headers(h -> h.set(HttpHeaders.CONTENT_LANGUAGE, "ua")); + b.headers(h -> h.setContentLanguage(Locale.ITALY)); + return b; + } + } + """; ProjectContext projectContext = TestProjectContext.buildProjectContext() .withBuildFileHavingDependencies( diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java index 46a76181f..8635eccb4 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -509,7 +509,6 @@ void created() { String expected = "" + "import org.springframework.http.ResponseEntity;\n" - + "\n" + "import java.net.URI;\n" + "\n" + "public class TestController {\n" @@ -641,7 +640,6 @@ void seeOther() { String expected = "" + "import org.springframework.http.HttpStatus;\n" + "import org.springframework.http.ResponseEntity;\n" - + "\n" + "import java.net.URI;\n" + "\n" + "public class TestController {\n" @@ -730,7 +728,6 @@ void temporaryRedirect() { String expected = "" + "import org.springframework.http.HttpStatus;\n" + "import org.springframework.http.ResponseEntity;\n" - + "\n" + "import java.net.URI;\n" + "\n" + "public class TestController {\n" From 98dbbb1b04c5034b8a9f749edd5d6bb3604562ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Tue, 6 Aug 2024 13:46:55 +0200 Subject: [PATCH 18/19] It all works --- components/jaxrs-recipes/pom.xml | 1 + .../java/example/recipe/SbmAdapterRecipe.java | 143 +++++--------- .../sbm/FreemarkerConfiguration.java | 30 +++ .../sbm/UserInteractionsDummy.java | 32 +++ .../conditions/HasTypeAnnotation.java | 7 +- .../sbm/jee/jaxrs/MigrateJaxRsRecipe.java | 18 +- .../actions/ConvertJaxRsAnnotations.java | 184 +----------------- .../sbm/JaxRsThroughAdapterTest.java | 133 ++++++------- .../jee/jaxrs/bootify-jaxrs/given/pom.xml | 5 + .../conditions/NoExactDependencyExist.java | 2 +- 10 files changed, 196 insertions(+), 359 deletions(-) create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.java create mode 100644 components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java diff --git a/components/jaxrs-recipes/pom.xml b/components/jaxrs-recipes/pom.xml index 0aeb5c0d8..e98c3370c 100644 --- a/components/jaxrs-recipes/pom.xml +++ b/components/jaxrs-recipes/pom.xml @@ -212,6 +212,7 @@ + diff --git a/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java index 8d4b774a1..c632b4f2b 100644 --- a/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java +++ b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java @@ -16,16 +16,29 @@ package example.recipe; +import freemarker.template.Configuration; import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.maven.MavenSettings; +import org.openrewrite.maven.cache.LocalMavenArtifactCache; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.rewrite.parser.JavaParserBuilder; +import org.springframework.rewrite.parser.maven.MavenSettingsInitializer; +import org.springframework.rewrite.parser.maven.RewriteMavenArtifactDownloader; import org.springframework.rewrite.resource.*; +import org.springframework.rewrite.scopes.ProjectMetadata; +import org.springframework.sbm.build.impl.MavenBuildFileRefactoringFactory; +import org.springframework.sbm.build.impl.RewriteMavenParser; +import org.springframework.sbm.build.resource.BuildFileResourceWrapper; import org.springframework.sbm.engine.context.ProjectContext; import org.springframework.sbm.engine.context.ProjectContextFactory; +import org.springframework.sbm.engine.recipe.RewriteRecipeLoader; import org.springframework.sbm.java.JavaSourceProjectResourceWrapper; import org.springframework.sbm.java.refactoring.JavaRefactoringFactory; import org.springframework.sbm.java.refactoring.JavaRefactoringFactoryImpl; import org.springframework.sbm.java.util.BasePackageCalculator; +import org.springframework.sbm.jee.jaxrs.MigrateJaxRsRecipe; import org.springframework.sbm.jee.jaxrs.actions.ConvertJaxRsAnnotations; import org.springframework.sbm.project.resource.ProjectResourceSetHolder; import org.springframework.sbm.project.resource.ProjectResourceWrapper; @@ -78,17 +91,17 @@ public TreeVisitor getScanner(List acc) { } @Override - public Collection generate(List acc, ExecutionContext executionContext) { - Collection generate = super.generate(acc, executionContext); + public Collection generate(List generate, ExecutionContext executionContext) { +// Collection generate = super.generate(acc, executionContext); // create the required classes initBeans(executionContext); // transform nodes to SourceFiles - List sourceFiles = acc.stream().filter(SourceFile.class::isInstance).map(SourceFile.class::cast).toList(); + List sourceFiles = generate.stream().filter(SourceFile.class::isInstance).map(SourceFile.class::cast).toList(); // FIXME: base dir calculation is fake - Path baseDir = Path.of("/Users/fkrueger/projects/spring-boot-migrator/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given").toAbsolutePath().normalize(); //executionContext.getMessage("base.dir"); + Path baseDir = Path.of(".").resolve("testcode/jee/jaxrs/bootify-jaxrs/given").toAbsolutePath().normalize(); //executionContext.getMessage("base.dir"); // Create the SBM resource set abstraction ProjectResourceSet projectResourceSet = projectResourceSetFactory.create(baseDir, sourceFiles); @@ -96,7 +109,10 @@ public Collection generate(List acc, Execution ProjectContext pc = projectContextFactory.createProjectContext(baseDir, projectResourceSet); // Execute the SBM Action = the JAXRS Recipe - new ConvertJaxRsAnnotations().apply(pc); +// new ConvertJaxRsAnnotations().apply(pc); + + RewriteRecipeLoader recipeLoader = new RewriteRecipeLoader(); + new MigrateJaxRsRecipe().jaxRs(recipeLoader).apply(pc); // Merge back result List modifiedNodes = merge(sourceFiles, pc.getProjectResources()); @@ -104,77 +120,6 @@ public Collection generate(List acc, Execution return modifiedNodes; } - // @Override -// public List getRecipeList() { -// List recipeList = new ArrayList<>(); -// recipeList.add(new GenericOpenRewriteRecipe<>(() -> new TreeVisitor() { -// @Override -// public void visit(@Nullable List nodes, ExecutionContext executionContext) { -// super.visit(nodes, executionContext); -// } -// -// @Override -// public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext) { -// return super.visit(tree, executionContext); -// } -// -// @Override -// public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext, Cursor parent) { -// return super.visit(tree, executionContext, parent); -// } -// })); -// return recipeList; -// } - -// @Override -// public TreeVisitor getVisitor() { -// return new TreeVisitor() { -// -// private ProjectResourceSetFactory projectResourceSetFactory; -// private ProjectContextFactory projectContextFactory; -// -// @Override -// public Tree visitNonNull(Tree tree, ExecutionContext executionContext) { -// return super.visitNonNull(tree, executionContext); -// } -// -// @Override -// public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { -// return true; -// } -// -// @Override -// public void visit(@Nullable List nodes, ExecutionContext executionContext) { -// -// super.visit(nodes, executionContext); -// -// // create the required classes -// initBeans(executionContext); -// -// // transform nodes to SourceFiles -// List sourceFiles = nodes.stream().filter(SourceFile.class::isInstance).map(SourceFile.class::cast).toList(); -// -// // FIXME: base dir calculation is fake -// Path baseDir = executionContext.getMessage("base.dir"); -// -// // Create the SBM resource set abstraction -// ProjectResourceSet projectResourceSet = projectResourceSetFactory.create(baseDir, sourceFiles); -// // Create the SBM ProjectContext -// ProjectContext pc = projectContextFactory.createProjectContext(baseDir, projectResourceSet); -// -// // Execute the SBM Action = the JAXRS Recipe -// new ConvertJaxRsAnnotations().apply(pc); -// -// // Merge back result -// List modifiedNodes = merge(nodes, pc.getProjectResources()); -// -// // Process other -// super.visit(modifiedNodes, executionContext); -// } -// -// }; -// } - private List merge(List nodes, ProjectResourceSet projectResources) { // merge the changed results into the given list and return the result ArrayList merged = new ArrayList<>(); @@ -195,23 +140,35 @@ private int findPosition(List merged, RewriteSourceFileHolder projectResourceWrappers = new ArrayList<>(); - RewriteMigrationResultMerger merger = new RewriteMigrationResultMerger(sourceFileWrapper); - ProjectResourceSetHolder holder = new ProjectResourceSetHolder(executionContext, merger); - JavaRefactoringFactory refactoringFactory = new JavaRefactoringFactoryImpl(holder, executionContext); - projectResourceWrappers.add(new JavaSourceProjectResourceWrapper(refactoringFactory, parserBuilder, executionContext)); - - - projectResourceSetFactory = new ProjectResourceSetFactory(new RewriteMigrationResultMerger(sourceFileWrapper), sourceFileWrapper, executionContext); - ProjectResourceWrapperRegistry registry = new ProjectResourceWrapperRegistry(projectResourceWrappers); - BasePackageCalculator calculator = new BasePackageCalculator(sbmApplicationProperties); - - ProjectResourceSetFactory resourceSetFactory = new ProjectResourceSetFactory(merger, sourceFileWrapper, executionContext); - - projectContextFactory = new ProjectContextFactory(registry, holder, refactoringFactory, calculator, parserBuilder, executionContext, merger, resourceSetFactory); + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("org.springframework.freemarker", "org.springframework.sbm", "org.springframework.rewrite"); + ctx.register(Configuration.class); + this.projectContextFactory = ctx.getBean(ProjectContextFactory.class); + this.projectResourceSetFactory = ctx.getBean(ProjectResourceSetFactory.class); +// RewriteSourceFileWrapper sourceFileWrapper = new RewriteSourceFileWrapper(); +// SbmApplicationProperties sbmApplicationProperties = new SbmApplicationProperties(); +// JavaParserBuilder parserBuilder = new JavaParserBuilder(); +// List projectResourceWrappers = new ArrayList<>(); +// RewriteMigrationResultMerger merger = new RewriteMigrationResultMerger(sourceFileWrapper); +// ProjectResourceSetHolder holder = new ProjectResourceSetHolder(executionContext, merger); +// JavaRefactoringFactory refactoringFactory = new JavaRefactoringFactoryImpl(holder, executionContext); +// projectResourceWrappers.add(new JavaSourceProjectResourceWrapper(refactoringFactory, parserBuilder, executionContext)); +// ProjectMetadata projectMetadata = new ProjectMetadata(); +// MavenBuildFileRefactoringFactory buildFileRefactoringFactory = new MavenBuildFileRefactoringFactory(holder, new RewriteMavenParser(new MavenSettingsInitializer(executionContext, projectMetadata), executionContext), executionContext); +// projectResourceWrappers.add(new BuildFileResourceWrapper( +// event -> System.out.println(event), +// buildFileRefactoringFactory, +// executionContext, +// new RewriteMavenArtifactDownloader(new LocalMavenArtifactCache(Path.of(System.getProperty("user.dir")).resolve(".m2/repository")), new MavenSettings(), t -> {throw new RuntimeException(t);})) +// ); +// +// +// projectResourceSetFactory = new ProjectResourceSetFactory(new RewriteMigrationResultMerger(sourceFileWrapper), sourceFileWrapper, executionContext); +// ProjectResourceWrapperRegistry registry = new ProjectResourceWrapperRegistry(projectResourceWrappers); +// BasePackageCalculator calculator = new BasePackageCalculator(sbmApplicationProperties); +// +// ProjectResourceSetFactory resourceSetFactory = new ProjectResourceSetFactory(merger, sourceFileWrapper, executionContext); +// +// projectContextFactory = new ProjectContextFactory(registry, holder, refactoringFactory, calculator, parserBuilder, executionContext, merger, resourceSetFactory); } } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.java new file mode 100644 index 000000000..9cf436563 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021 - 2023 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; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Fabian Krüger + */ +@Configuration +public class FreemarkerConfiguration { + @Bean + public freemarker.template.Configuration configuration() { + return new freemarker.template.Configuration(); + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java new file mode 100644 index 000000000..b1991f2ca --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021 - 2023 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; + +import org.springframework.sbm.engine.recipe.UserInteractions; +import org.springframework.stereotype.Component; + +@Component +public class UserInteractionsDummy implements UserInteractions { + @Override + public boolean askUserYesOrNo(String question) { + return true; + } + + @Override + public String askForInput(String question) { + return "answer"; + } +} diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java index ce08240a8..408b95548 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java @@ -35,10 +35,9 @@ public String getDescription() { @Override public boolean evaluate(ProjectContext context) { - return true; -// context.getProjectJavaSources().asStream() -// .flatMap(js -> js.getTypes().stream()) -// .anyMatch(t -> t.hasAnnotation(annotation)); + return context.getProjectJavaSources().asStream() + .flatMap(js -> js.getTypes().stream()) + .anyMatch(t -> t.hasAnnotation(annotation)); } } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java index 97002c7a5..63be82df8 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java @@ -55,15 +55,15 @@ public Recipe jaxRs(RewriteRecipeLoader rewriteRecipeLoader) { .condition(HasImportStartingWith.builder().value("javax.ws.rs").description("Any class has import starting with javax.ws.rs").build()) .actions(List.of( - AddDependencies.builder() - .dependencies( - List.of( - Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-web").version("2.3.4.RELEASE").build() - ) - ) - .description("Add spring-boot-starter-web dependency to build file.") - .condition(NoExactDependencyExist.builder().dependency(Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-web").build()).build()) - .build(), +// AddDependencies.builder() +// .dependencies( +// List.of( +// Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-web").version("2.3.4.RELEASE").build() +// ) +// ) +// .description("Add spring-boot-starter-web dependency to build file.") +// .condition(NoExactDependencyExist.builder().dependency(Dependency.builder().groupId("org.springframework.boot").artifactId("spring-boot-starter-web").build()).build()) +// .build(), ConvertJaxRsAnnotations.builder() .condition(HasTypeAnnotation.builder().annotation("javax.ws.rs.Path").build()) diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java index 5b06f2277..652805cc0 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java @@ -282,22 +282,6 @@ private void transformTypeAnnotations(Type type) { } String rmAttrs = attributes.entrySet().stream().map(e -> e.getKey() + " = " + e.getValue().print()).collect(Collectors.joining(", ")); type.addAnnotation("@RequestMapping(" + rmAttrs + ")", "org.springframework.web.bind.annotation.RequestMapping", Set.of(""" - /* - * Copyright 2002-2024 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.web.bind.annotation; import java.lang.annotation.Documented; @@ -309,57 +293,6 @@ private void transformTypeAnnotations(Type type) { import org.springframework.aot.hint.annotation.Reflective; import org.springframework.core.annotation.AliasFor; - /** - * Annotation for mapping web requests onto methods in request-handling classes - * with flexible method signatures. - * - *

Both Spring MVC and Spring WebFlux support this annotation through a - * {@code RequestMappingHandlerMapping} and {@code RequestMappingHandlerAdapter} - * in their respective modules and package structures. For the exact list of - * supported handler method arguments and return types in each, please use the - * reference documentation links below: - *

- * - *

NOTE: This annotation can be used both at the class and - * at the method level. In most cases, at the method level applications will - * prefer to use one of the HTTP method specific variants - * {@link GetMapping @GetMapping}, {@link PostMapping @PostMapping}, - * {@link PutMapping @PutMapping}, {@link DeleteMapping @DeleteMapping}, or - * {@link PatchMapping @PatchMapping}. - * - *

NOTE: This annotation cannot be used in conjunction with - * other {@code @RequestMapping} annotations that are declared on the same element - * (class, interface, or method). If multiple {@code @RequestMapping} annotations - * are detected on the same element, a warning will be logged, and only the first - * mapping will be used. This also applies to composed {@code @RequestMapping} - * annotations such as {@code @GetMapping}, {@code @PostMapping}, etc. - * - *

NOTE: When using controller interfaces (e.g. for AOP proxying), - * make sure to consistently put all your mapping annotations — such - * as {@code @RequestMapping} and {@code @SessionAttributes} — on - * the controller interface rather than on the implementation class. - * - * @author Juergen Hoeller - * @author Arjen Poutsma - * @author Sam Brannen - * @since 2.5 - * @see GetMapping - * @see PostMapping - * @see PutMapping - * @see DeleteMapping - * @see PatchMapping - */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @@ -367,137 +300,22 @@ private void transformTypeAnnotations(Type type) { @Reflective(ControllerMappingReflectiveProcessor.class) public @interface RequestMapping { - /** - * Assign a name to this mapping. - *

Supported at the type level as well as at the method level! - * When used on both levels, a combined name is derived by concatenation - * with "#" as separator. - * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder - * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy - */ String name() default ""; - /** - * The path mapping URIs — for example, {@code "/profile"}. - *

This is an alias for {@link #path}. For example, - * {@code @RequestMapping("/profile")} is equivalent to - * {@code @RequestMapping(path="/profile")}. - *

See {@link #path} for further details. - */ @AliasFor("path") String[] value() default {}; - /** - * The path mapping URIs — for example, {@code "/profile"}. - *

Ant-style path patterns are also supported (e.g. {@code "/profile/**"}). - * At the method level, relative paths (e.g. {@code "edit"}) are supported - * within the primary mapping expressed at the type level. - * Path mapping URIs may contain placeholders (e.g. "/${profile_path}"). - *

Supported at the type level as well as at the method level! - * When used at the type level, all method-level mappings inherit - * this primary mapping, narrowing it for a specific handler method. - *

NOTE: A handler method that is not mapped to any path - * explicitly is effectively mapped to an empty path. - * @since 4.2 - */ @AliasFor("value") String[] path() default {}; - /** - * The HTTP request methods to map to, narrowing the primary mapping: - * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. - *

Supported at the type level as well as at the method level! - * When used at the type level, all method-level mappings inherit this - * HTTP method restriction. - */ RequestMethod[] method() default {}; - /** - * The parameters of the mapped request, narrowing the primary mapping. - *

Same format for any environment: a sequence of "myParam=myValue" style - * expressions, with a request only mapped if each such parameter is found - * to have the given value. Expressions can be negated by using the "!=" operator, - * as in "myParam!=myValue". "myParam" style expressions are also supported, - * with such parameters having to be present in the request (allowed to have - * any value). Finally, "!myParam" style expressions indicate that the - * specified parameter is not supposed to be present in the request. - *

Supported at the type level as well as at the method level! - * When used at the type level, all method-level mappings inherit this - * parameter restriction. - */ String[] params() default {}; - /** - * The headers of the mapped request, narrowing the primary mapping. - *

Same format for any environment: a sequence of "My-Header=myValue" style - * expressions, with a request only mapped if each such header is found - * to have the given value. Expressions can be negated by using the "!=" operator, - * as in "My-Header!=myValue". "My-Header" style expressions are also supported, - * with such headers having to be present in the request (allowed to have - * any value). Finally, "!My-Header" style expressions indicate that the - * specified header is not supposed to be present in the request. - *

Also supports media type wildcards (*), for headers such as Accept - * and Content-Type. For instance, - *

-                    	 * @RequestMapping(value = "/something", headers = "content-type=text/*")
-                    	 * 
- * will match requests with a Content-Type of "text/html", "text/plain", etc. - *

Supported at the type level as well as at the method level! - * When used at the type level, all method-level mappings inherit this - * header restriction. - * @see org.springframework.http.MediaType - */ String[] headers() default {}; - - /** - * Narrows the primary mapping by media types that can be consumed by the - * mapped handler. Consists of one or more media types one of which must - * match to the request {@code Content-Type} header. Examples: - *

-                    	 * consumes = "text/plain"
-                    	 * consumes = {"text/plain", "application/*"}
-                    	 * consumes = MediaType.TEXT_PLAIN_VALUE
-                    	 * 
- *

If a declared media type contains a parameter, and if the - * {@code "content-type"} from the request also has that parameter, then - * the parameter values must match. Otherwise, if the media type from the - * request {@code "content-type"} does not contain the parameter, then the - * parameter is ignored for matching purposes. - *

Expressions can be negated by using the "!" operator, as in - * "!text/plain", which matches all requests with a {@code Content-Type} - * other than "text/plain". - *

Supported at the type level as well as at the method level! - * If specified at both levels, the method level consumes condition overrides - * the type level condition. - * @see org.springframework.http.MediaType - * @see jakarta.servlet.http.HttpServletRequest#getContentType() - */ + String[] consumes() default {}; - /** - * Narrows the primary mapping by media types that can be produced by the - * mapped handler. Consists of one or more media types one of which must - * be chosen via content negotiation against the "acceptable" media types - * of the request. Typically those are extracted from the {@code "Accept"} - * header but may be derived from query parameters, or other. Examples: - *

-                    	 * produces = "text/plain"
-                    	 * produces = {"text/plain", "application/*"}
-                    	 * produces = MediaType.TEXT_PLAIN_VALUE
-                    	 * produces = "text/plain;charset=UTF-8"
-                    	 * 
- *

If a declared media type contains a parameter (e.g. "charset=UTF-8", - * "type=feed", "type=entry") and if a compatible media type from the request - * has that parameter too, then the parameter values must match. Otherwise, - * if the media type from the request does not contain the parameter, it is - * assumed the client accepts any value. - *

Expressions can be negated by using the "!" operator, as in "!text/plain", - * which matches all requests with a {@code Accept} other than "text/plain". - *

Supported at the type level as well as at the method level! - * If specified at both levels, the method level produces condition overrides - * the type level condition. - * @see org.springframework.http.MediaType - */ String[] produces() default {}; } diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java index afd61cea0..a1aeb136a 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java @@ -95,8 +95,7 @@ class WithRewriteTest implements RewriteTest { """; // FIXME: Use Openrewrite to retrieve dependency Paths that must be on classpath // mvn dependency:build-classpath -Dmdep.outputFile=cp.txt - List deps = Arrays.stream("/Users/fkrueger/.m2/repository/org/jboss/spec/javax/ws/rs/jboss-jaxrs-api_2.1_spec/1.0.1.Final/jboss-jaxrs-api_2.1_spec-1.0.1.Final.jar".split(":")).map(d -> Path.of(d)).toList(); - private JavaParser.Builder javaParserBuilder = JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true).classpath(deps); + private JavaParser.Builder javaParserBuilder = JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true); @Override public void defaults(RecipeSpec spec) { @@ -113,20 +112,6 @@ public void defaults(RecipeSpec spec) { .activateRecipes(RECIPE)); } - public static List getDependencyJarsForClasspath(String pom) { - try { - Xml.Document doc = (Xml.Document) MavenParser.builder().build().parse(pom).toList().get(0); - MavenResolutionResult resolutionResult = doc.getMarkers().findFirst(MavenResolutionResult.class).orElseThrow(); - resolutionResult = resolutionResult.resolveDependencies(new MavenPomDownloader(Collections.emptyMap(), new InMemoryExecutionContext(), null, null), new InMemoryExecutionContext()); - List resolvedDependencies = resolutionResult.getDependencies().get(Scope.Compile); - MavenArtifactDownloader downloader = new MavenArtifactDownloader(new LocalMavenArtifactCache(Paths.get(System.getProperty("user.home"), ".m2", "repository")), null, (t) -> { - }); - return resolvedDependencies.stream().filter(d -> "jar".equals(d.getType())).map(downloader::downloadArtifact).toList(); - } catch (MavenDownloadingExceptions e) { - throw new RuntimeException(e); - } - } - @Test public void simple() { rewriteRun( @@ -160,6 +145,8 @@ public String getHelloWorldJSON(@PathParam("name") String name) throws Exception """, //language=Java """ + package com.example.jee.app; + import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -192,7 +179,7 @@ public void theTest() { rewriteRun( (spec) -> spec.expectedCyclesThatMakeChanges(2), - mavenProject("project", + mavenProject("", // this affects the resource getSourcePath() Assertions.java( //language=Java """ @@ -242,56 +229,52 @@ private boolean isResponseStatusSuccessful(Response.Status.Family family) { } """ - , - //language=Java - """ - package com.example.jee.app; - - import org.springframework.web.bind.annotation.RequestMapping; - import org.springframework.web.bind.annotation.RequestMethod; - import org.springframework.web.bind.annotation.RestController; +// , +// //language=Java +// """ +// package com.example.jee.app; +// +// import org.springframework.web.bind.annotation.RequestMapping; +// import org.springframework.web.bind.annotation.RequestMethod; +// import org.springframework.web.bind.annotation.RestController; +// +// import org.springframework.http.HttpStatus; +// import org.springframework.http.HttpStatus.Series; +// import org.springframework.web.bind.annotation.PathVariable; +// import org.springframework.web.bind.annotation.RequestParam; +// +// +// @RestController +// @RequestMapping(value = "/", produces = "application/json") +// public class PersonController { +// +// @RequestMapping(value = "/json/{name}", consumes = "application/json", method = RequestMethod.POST) +// public String getHelloWorldJSON(@PathVariable("name") String name) throws Exception { +// System.out.println("name: " + name); +// return "{\\"Hello\\":\\"" + name + "\\""; +// } +// +// @RequestMapping(value = "/json", produces = APPLICATION_JSON, consumes = APPLICATION_JSON, method = RequestMethod.GET) +// 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...\\""; +// } +// +// +// @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) +// public String getHelloWorldXML(@PathVariable("name") String name) throws Exception { +// System.out.println("name: " + name); +// return "Hello "+name+""; +// } +// +// private boolean isResponseStatusSuccessful(HttpStatus.Series family) { +// return family == Series.SUCCESSFUL; +// } +// +// } +// """ - import javax.ws.rs.DefaultValue; - import javax.ws.rs.PathParam; - import javax.ws.rs.QueryParam; - import javax.ws.rs.core.MediaType; - import javax.ws.rs.core.Response; - - import static javax.ws.rs.core.MediaType.APPLICATION_JSON; - import static javax.ws.rs.core.Response.Status.Family; - - - @RestController - @RequestMapping(value = "/", produces = "application/json") - public class PersonController { - - @RequestMapping(value = "/json/{name}", consumes = "application/json", method = RequestMethod.POST) - public String getHelloWorldJSON(@PathParam("name") String name) throws Exception { - System.out.println("name: " + name); - return "{\\"Hello\\":\\"" + name + "\\""; - } - - @RequestMapping(value = "/json", produces = APPLICATION_JSON, consumes = APPLICATION_JSON, method = RequestMethod.GET) - public String getAllPersons(@QueryParam("q") String searchBy, @DefaultValue("0") @QueryParam("page") int page) throws Exception { - return "{\\"message\\":\\"No person here...\\""; - } - - - @RequestMapping(value = "/xml/{name}", produces = MediaType.APPLICATION_XML, consumes = MediaType.APPLICATION_XML, method = RequestMethod.POST) - public String getHelloWorldXML(@PathParam("name") String name) throws Exception { - System.out.println("name: " + name); - return "Hello "+name+""; - } - - private boolean isResponseStatusSuccessful(Response.Status.Family family) { - return family == Family.SUCCESSFUL; - } - - } - """ ), pomXml( - //language=xml POM_XML ) ) @@ -320,11 +303,9 @@ void jaxRsRecipesThroughAdapterInOpenrewrite(@TempDir Path tmpDir) throws IOExce String mavenPluginVersion = ""; String gradlePluginVersion = ""; Path baseDir = tmpDir; - -// mvn -B --fail-at-end org.openrewrite.maven:rewrite-maven-plugin:5.32.1:dryRun \ -// -Drewrite.activeRecipes=example.recipe.SbmAdapterRecipe \  -// -Drewrite.recipeArtifactCoordinates=org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT \ -// -Dmaven.opts=“-Xms256M -Xmx1024M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005“ + // mvn -B --fail-at-end -Drewrite.activeRecipes=example.recipe.SbmAdapterRecipe -Drewrite.recipeArtifactCoordinates=org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT -Dmaven.opts="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8000" org.openrewrite.maven:rewrite-maven-plugin:5.32.1:dryRun +// mvn -B --fail-at-end -Drewrite.activeRecipes=example.recipe.SbmAdapterRecipe -Drewrite.recipeArtifactCoordinates=org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT -Dmaven.opts=“-Xms256M -Xmx1024M“ -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 org.openrewrite.maven:rewrite-maven-plugin:5.32.1:dryRun +// mvn -B --fail-at-end -Drewrite.activeRecipes=example.recipe.SbmAdapterRecipe -Drewrite.recipeArtifactCoordinates=org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT -Dmaven.opts=“-Xms256M -Xmx1024M“ -Xagentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 org.openrewrite.maven:rewrite-maven-plugin:5.32.1:dryRun PluginInvocationResult pluginInvocationResult = RewritePlugin.run() @@ -339,8 +320,22 @@ void jaxRsRecipesThroughAdapterInOpenrewrite(@TempDir Path tmpDir) throws IOExce System.out.println(pluginInvocationResult.capturedOutput()); + System.out.println(Files.readString(to.resolve("src/main/java/com/example/jee/app/PersonController.java"))); } } + public static List getDependencyJarsForClasspath(String pom) { + try { + Xml.Document doc = (Xml.Document) MavenParser.builder().build().parse(pom).toList().get(0); + MavenResolutionResult resolutionResult = doc.getMarkers().findFirst(MavenResolutionResult.class).orElseThrow(); + resolutionResult = resolutionResult.resolveDependencies(new MavenPomDownloader(Collections.emptyMap(), new InMemoryExecutionContext(), null, null), new InMemoryExecutionContext()); + List resolvedDependencies = resolutionResult.getDependencies().get(Scope.Compile); + MavenArtifactDownloader downloader = new MavenArtifactDownloader(new LocalMavenArtifactCache(Paths.get(System.getProperty("user.home"), ".m2", "repository")), null, (t) -> { + }); + return resolvedDependencies.stream().filter(d -> "jar".equals(d.getType())).map(downloader::downloadArtifact).toList(); + } catch (MavenDownloadingExceptions e) { + throw new RuntimeException(e); + } + } } diff --git a/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml b/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml index 5133f242f..22d71ce93 100644 --- a/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml +++ b/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml @@ -28,6 +28,11 @@ jboss-jaxrs-api_2.1_spec 1.0.1.Final + + org.springframework.boot + spring-boot-starter-freemarker + 3.3.1 + diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/build/migration/conditions/NoExactDependencyExist.java b/components/sbm-core/src/main/java/org/springframework/sbm/build/migration/conditions/NoExactDependencyExist.java index cfddc96a3..b2a712c73 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/build/migration/conditions/NoExactDependencyExist.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/build/migration/conditions/NoExactDependencyExist.java @@ -37,6 +37,6 @@ public String getDescription() { @Override public boolean evaluate(ProjectContext context) { - return false == context.getBuildFile().hasExactDeclaredDependency(dependency); + return false == context.getApplicationModules().getRootModule().getBuildFile().hasExactDeclaredDependency(dependency); } } From b32a639f736c54b0ee57bf786aed711c0a17312f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Tue, 6 Aug 2024 19:02:58 +0200 Subject: [PATCH 19/19] It all works (kind of) --- components/jaxrs-recipes/pom.xml | 11 ---- .../java/example/recipe/SbmAdapterRecipe.java | 60 +++---------------- .../sbm/FreemarkerConfiguration.java | 2 + .../sbm/UserInteractionsDummy.java | 2 + .../sbm/JaxRsThroughAdapterTest.java | 2 +- .../BootifyJaxRsAnnotationsRecipeTest.java | 2 +- .../sbm/test/UserInteractionsDummy.java | 2 +- 7 files changed, 15 insertions(+), 66 deletions(-) diff --git a/components/jaxrs-recipes/pom.xml b/components/jaxrs-recipes/pom.xml index e98c3370c..adaabb971 100644 --- a/components/jaxrs-recipes/pom.xml +++ b/components/jaxrs-recipes/pom.xml @@ -109,11 +109,6 @@ - - - - - com.tngtech.archunit @@ -206,12 +201,6 @@ 8.29.0 test - - - - - - diff --git a/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java index c632b4f2b..c1543ac77 100644 --- a/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java +++ b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java @@ -92,13 +92,11 @@ public TreeVisitor getScanner(List acc) { @Override public Collection generate(List generate, ExecutionContext executionContext) { -// Collection generate = super.generate(acc, executionContext); - // create the required classes - initBeans(executionContext); + initBeans(); // transform nodes to SourceFiles - List sourceFiles = generate.stream().filter(SourceFile.class::isInstance).map(SourceFile.class::cast).toList(); + List sourceFiles = generate; // FIXME: base dir calculation is fake Path baseDir = Path.of(".").resolve("testcode/jee/jaxrs/bootify-jaxrs/given").toAbsolutePath().normalize(); //executionContext.getMessage("base.dir"); @@ -114,61 +112,19 @@ public Collection generate(List generate, Exec RewriteRecipeLoader recipeLoader = new RewriteRecipeLoader(); new MigrateJaxRsRecipe().jaxRs(recipeLoader).apply(pc); - // Merge back result - List modifiedNodes = merge(sourceFiles, pc.getProjectResources()); - - return modifiedNodes; - } - - private List merge(List nodes, ProjectResourceSet projectResources) { - // merge the changed results into the given list and return the result - ArrayList merged = new ArrayList<>(); - merged.addAll(nodes); - projectResources.stream() - .filter(r -> r.hasChanges()) - .forEach(changed -> { - int pos = findPosition(merged, changed); - merged.add(pos, changed.getSourceFile()); - }); - return merged; - } + List list = pc.getProjectResources().stream() + .map(pr -> pr.getSourceFile()) + .toList(); - private int findPosition(List merged, RewriteSourceFileHolder changed) { - List paths = merged.stream().map(f -> f.getSourcePath().toString()).toList(); - return paths.indexOf(changed.getSourcePath().toString()); + return list; } - - private void initBeans(ExecutionContext executionContext) { + private void initBeans() { + // ExecutionContext is not retrieved from OpenRewrite here AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("org.springframework.freemarker", "org.springframework.sbm", "org.springframework.rewrite"); ctx.register(Configuration.class); this.projectContextFactory = ctx.getBean(ProjectContextFactory.class); this.projectResourceSetFactory = ctx.getBean(ProjectResourceSetFactory.class); -// RewriteSourceFileWrapper sourceFileWrapper = new RewriteSourceFileWrapper(); -// SbmApplicationProperties sbmApplicationProperties = new SbmApplicationProperties(); -// JavaParserBuilder parserBuilder = new JavaParserBuilder(); -// List projectResourceWrappers = new ArrayList<>(); -// RewriteMigrationResultMerger merger = new RewriteMigrationResultMerger(sourceFileWrapper); -// ProjectResourceSetHolder holder = new ProjectResourceSetHolder(executionContext, merger); -// JavaRefactoringFactory refactoringFactory = new JavaRefactoringFactoryImpl(holder, executionContext); -// projectResourceWrappers.add(new JavaSourceProjectResourceWrapper(refactoringFactory, parserBuilder, executionContext)); -// ProjectMetadata projectMetadata = new ProjectMetadata(); -// MavenBuildFileRefactoringFactory buildFileRefactoringFactory = new MavenBuildFileRefactoringFactory(holder, new RewriteMavenParser(new MavenSettingsInitializer(executionContext, projectMetadata), executionContext), executionContext); -// projectResourceWrappers.add(new BuildFileResourceWrapper( -// event -> System.out.println(event), -// buildFileRefactoringFactory, -// executionContext, -// new RewriteMavenArtifactDownloader(new LocalMavenArtifactCache(Path.of(System.getProperty("user.dir")).resolve(".m2/repository")), new MavenSettings(), t -> {throw new RuntimeException(t);})) -// ); -// -// -// projectResourceSetFactory = new ProjectResourceSetFactory(new RewriteMigrationResultMerger(sourceFileWrapper), sourceFileWrapper, executionContext); -// ProjectResourceWrapperRegistry registry = new ProjectResourceWrapperRegistry(projectResourceWrappers); -// BasePackageCalculator calculator = new BasePackageCalculator(sbmApplicationProperties); -// -// ProjectResourceSetFactory resourceSetFactory = new ProjectResourceSetFactory(merger, sourceFileWrapper, executionContext); -// -// projectContextFactory = new ProjectContextFactory(registry, holder, refactoringFactory, calculator, parserBuilder, executionContext, merger, resourceSetFactory); } } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.java index 9cf436563..c5f2dedae 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.sbm; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -24,6 +25,7 @@ @Configuration public class FreemarkerConfiguration { @Bean + @ConditionalOnMissingBean(type = "freemarker.template.Configuration") public freemarker.template.Configuration configuration() { return new freemarker.template.Configuration(); } diff --git a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java index b1991f2ca..d604ac178 100644 --- a/components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java @@ -15,10 +15,12 @@ */ package org.springframework.sbm; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.sbm.engine.recipe.UserInteractions; import org.springframework.stereotype.Component; @Component +@ConditionalOnMissingBean(type = "org.springframework.sbm.UserInteractions") public class UserInteractionsDummy implements UserInteractions { @Override public boolean askUserYesOrNo(String question) { diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java index a1aeb136a..4dc68a0ff 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java @@ -300,7 +300,7 @@ void jaxRsRecipesThroughAdapterInOpenrewrite(@TempDir Path tmpDir) throws IOExce FileUtils.forceMkdir(to.toFile()); FileUtils.copyDirectory(from.toFile(), to.toFile()); - String mavenPluginVersion = ""; + String mavenPluginVersion = "5.35.0"; String gradlePluginVersion = ""; Path baseDir = tmpDir; // mvn -B --fail-at-end -Drewrite.activeRecipes=example.recipe.SbmAdapterRecipe -Drewrite.recipeArtifactCoordinates=org.springframework.sbm:jaxrs-recipes:0.15.2-SNAPSHOT -Dmaven.opts="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8000" org.openrewrite.maven:rewrite-maven-plugin:5.32.1:dryRun diff --git a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java index 63160697c..e69b46a84 100644 --- a/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java @@ -36,7 +36,7 @@ void test() { Optional recipe = Optional.of(jaxRsRecipe); RecipeTestSupport.assertThatRecipeExists(recipe); RecipeTestSupport.assertThatRecipeHasActions(recipe, - AddDependencies.class, +// AddDependencies.class, ConvertJaxRsAnnotations.class, ReplaceTypeAction.class, ReplaceTypeAction.class, diff --git a/components/recipe-test-support/src/main/java/org/springframework/sbm/test/UserInteractionsDummy.java b/components/recipe-test-support/src/main/java/org/springframework/sbm/test/UserInteractionsDummy.java index d3c4b1b39..4e7c05ef7 100644 --- a/components/recipe-test-support/src/main/java/org/springframework/sbm/test/UserInteractionsDummy.java +++ b/components/recipe-test-support/src/main/java/org/springframework/sbm/test/UserInteractionsDummy.java @@ -18,7 +18,7 @@ import org.springframework.sbm.engine.recipe.UserInteractions; import org.springframework.stereotype.Component; -@Component +//@Component public class UserInteractionsDummy implements UserInteractions { @Override public boolean askUserYesOrNo(String question) {