diff --git a/components/jaxrs-recipes/pom.xml b/components/jaxrs-recipes/pom.xml new file mode 100644 index 000000000..adaabb971 --- /dev/null +++ b/components/jaxrs-recipes/pom.xml @@ -0,0 +1,240 @@ + + + + + 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.recipe + rewrite-recipe-bom + 2.14.0 + pom + import + + + + + + + + org.openrewrite.maven + rewrite-maven-plugin + 5.20.0 + + + + + org.openrewrite + rewrite-core + + + org.openrewrite + rewrite-java + + + + org.springframework.rewrite + spring-rewrite-commons-plugin-invoker-polyglot + 0.1.0-SNAPSHOT + test + + + + + 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 + + + org.openrewrite.recipe + rewrite-static-analysis + 1.11.0 + compile + + + org.openrewrite + rewrite-test + 8.29.0 + test + + + + + + + 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/main/java/example/recipe/SbmAdapterRecipe.java b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java new file mode 100644 index 000000000..c1543ac77 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/example/recipe/SbmAdapterRecipe.java @@ -0,0 +1,130 @@ +/* + * 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 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; +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.Collection; +import java.util.List; + +/** + * @author Fabian Krüger + */ +public class SbmAdapterRecipe extends ScanningRecipe> { + + private ProjectResourceSetFactory projectResourceSetFactory; + private ProjectContextFactory projectContextFactory; + + @Override + public @NlsRewrite.DisplayName String getDisplayName() { + return ""; + } + + @Override + public @NlsRewrite.Description String getDescription() { + return ""; + } + + public SbmAdapterRecipe() { + + } + + @Override + 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); + } + }; + } + + @Override + public Collection generate(List generate, ExecutionContext executionContext) { + // create the required classes + initBeans(); + + // transform nodes to SourceFiles + 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"); + + // 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); + + RewriteRecipeLoader recipeLoader = new RewriteRecipeLoader(); + new MigrateJaxRsRecipe().jaxRs(recipeLoader).apply(pc); + + List list = pc.getProjectResources().stream() + .map(pr -> pr.getSourceFile()) + .toList(); + + return list; + } + + 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); + } + +} 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..c5f2dedae --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/FreemarkerConfiguration.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.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Fabian Krüger + */ +@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 new file mode 100644 index 000000000..d604ac178 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/UserInteractionsDummy.java @@ -0,0 +1,34 @@ +/* + * 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.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) { + 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 new file mode 100644 index 000000000..408b95548 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/conditions/HasTypeAnnotation.java @@ -0,0 +1,43 @@ +/* + * 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 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..d8007be3c --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/RewriteMethodInvocation.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.java.migration.recipes; + +import lombok.RequiredArgsConstructor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.NlsRewrite; +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 "Rewrite method invocation"; + } + + @Override + public @NlsRewrite.Description String getDescription() { + return getDisplayName(); + } + + @Override + public 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..f35ee7045 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/java/migration/recipes/openrewrite/ReplaceConstantWithAnotherConstant.java @@ -0,0 +1,133 @@ +/* + * 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."; + } + + + + // FIXME: (jaxrs) Call from visitor to decide if applicable + protected TreeVisitor getSingleSourceApplicableTest() { + return new UsesType<>(existingFullyQualifiedConstantName.substring(0,existingFullyQualifiedConstantName.lastIndexOf('.')), true); + } + + @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); + + fieldAccess = JavaTemplate.builder(template).imports(owningType).build() + .apply(getCursor(), fieldAccess.getCoordinates().replace()) + .withPrefix(fieldAccess.getPrefix()); + + return fieldAccess; + } + 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 JavaTemplate.builder(template) + .imports(owningType) + .build() + .apply(getCursor(), 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..ee4a1dc87 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/ejb/actions/MigrateJndiLookup.java @@ -0,0 +1,192 @@ +/* + * 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.openrewrite.staticanalysis.RemoveUnusedLocalVariables; +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.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) { + 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 { + + 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("@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); + 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..63be82df8 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/MigrateJaxRsRecipe.java @@ -0,0 +1,157 @@ +/* + * 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 { + + + @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()) + .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()) + .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..652805cc0 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotations.java @@ -0,0 +1,325 @@ +/* + * 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(")"); + } + + 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"); + + } + } + + 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()); + 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) { + 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", 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 {}; + + } + """)); + } + } +} 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..c0d640c64 --- /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; + + protected TreeVisitor getSingleSourceApplicableTest() { + return new UsesType<>(sourceAnnotationType, true); + } + + @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 + @NotNull + public 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..9db9f3e5f --- /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."; + } + + protected TreeVisitor getSingleSourceApplicableTest() { + // FIXME: (jaxrs) Test in visitor + return new UsesType<>(annotationTypeToRemove, true); + } + + @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..388706e56 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaType.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.jaxrs.recipes; + +import org.openrewrite.NlsRewrite; +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.ArrayList; +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 { + + @Override + public List getRecipeList() { + + List recipeList = new ArrayList<>(); + + // 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) -> recipeList.add(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.MediaType." + key,"org.springframework.http.MediaType." + value)) + ); + + 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) + 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"); + 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) + 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 template.apply(v.getCursor(), 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) + 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 template.apply(v.getCursor(), m.getCoordinates().replace()); + })); + + // MediaType(String, String) - present on Spring MediaType + 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) + 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("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 template.apply(v.getCursor(), m.getCoordinates().replace(), arguments.get(0), arguments.get(1), arguments.get(2)); + })); + + // MediaType(String, String, Map) - present on Spring MediaType + 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 + recipeList.add(new ChangeType("javax.ws.rs.core.MediaType", "org.springframework.http.MediaType", false)); + + return recipeList; + } + + @Override + 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 new file mode 100644 index 000000000..0f0ffa5af --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceRequestParameterProperties.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.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 { + + @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 new file mode 100644 index 000000000..86b993db3 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceResponseEntityBuilder.java @@ -0,0 +1,253 @@ +/* + * 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.NlsRewrite; +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 { + + @Override + public List getRecipeList() { + List recipeList = new ArrayList<>(); + + // #allow(String...) + 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("#{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 t.apply(v.getCursor(), m.getCoordinates().replace(), parameters.toArray()); + } + ) + ); + + // #allow(Set) + recipeList.add(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder allow(java.util.Set)"), + (v, m, addImport) -> { + 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 t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #cacheControl(CacheControl) + + // #encoding(String) + recipeList.add(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder encoding(java.lang.String)"), + (v, m, addImport) -> { + 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 t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments()); + } + ) + ); + + // #contentLocation(URI) + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder contentLocation(java.net.URI)"), "location", "org.springframework.http.ResponseEntity.HeadersBuilder")); + + // #tag(String) + 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[]) + 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 JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}").build().apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + } + ) + ); + + // #expires(Date) + recipeList.add(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder expires(java.util.Date)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.setExpires(#{any()}.toInstant()))") + .build(); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #language(String) + recipeList.add(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder language(java.lang.String)"), + (v, m, addImport) -> { + 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 t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #language(Locale) + recipeList.add(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder language(java.util.Locale)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.headers(h -> h.setContentLanguage(#{any()}))") + .build(); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #lastModified(Date) + recipeList.add(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder lastModified(java.util.Date)"), + (v, m, addImport) -> { + JavaTemplate t = JavaTemplate.builder("#{any(org.springframework.http.ResponseEntity.HeadersBuilder)}.lastModified(#{any()}.toInstant())") + .build(); + return t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #location(URI) - present on Spring ResponseEntity builder classes, nothing to do + + // replaceAll(MultivaluedMap) + 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("#{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 t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #type(String) + recipeList.add(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.ResponseBuilder type(java.lang.String)"), + (v, m, addImport) -> { + 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 t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #type(MediaType) + 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(); + 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("#{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)); + } + ) + ); + + /* + * 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 + 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); + 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)); + } + ) + ); + + recipeList.add(new AdjustTypesFromExpressionMarkers()); + + // Finally replace type with BodyBuilder if nothing else replaced it previously + recipeList.add(new ChangeType("javax.ws.rs.core.Response$ResponseBuilder", "org.springframework.http.ResponseEntity$BodyBuilder", true)); + + return recipeList; + } + + @Override + 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 new file mode 100644 index 000000000..e4c0a77b9 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapCacheControl.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.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 { + + @Override + public List getRecipeList() { + + List recipeList = new ArrayList<>(); + + /* + * 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) + 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 t.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + })); + + // constructor + 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 t.apply(v.getCursor(), c.getCoordinates().replace()); + })); + + recipeList.add(new ChangeType("javax.ws.rs.core.CacheControl", "org.springframework.http.CacheControl", false)); + + return recipeList; + } + + @Override + 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 new file mode 100644 index 000000000..edaf215b6 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapFamilyForSeries.java @@ -0,0 +1,74 @@ +/* + * 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.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 { + + @Override + public List getRecipeList() { + + List recipeList = new ArrayList<>(); + + 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) -> 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 + recipeList.add(new RewriteMethodInvocation( + RewriteMethodInvocation.methodInvocationMatcher("javax.ws.rs.core.Response.Status.Family familyOf(int)"), + (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("HttpStatus.Series.resolve(#{any(int)})").build(); + v.maybeAddImport("org.springframework.http.HttpStatus.Series"); + addImport.accept("org.springframework.http.HttpStatus"); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); + } + ) + ); + + recipeList.add(new ChangeType("javax.ws.rs.core.Response$Status$Family", "org.springframework.http.HttpStatus$Series", true)); + return recipeList; + } + + @Override + 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 new file mode 100644 index 000000000..742d28389 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapHttHeaders.java @@ -0,0 +1,154 @@ +/* + * 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.NlsRewrite; +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.ArrayList; +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 { + + + @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") + * - LAST_EVENT_ID_HEADER (possibly replace with "Last-Event-ID" + * - getAcceptableMediaTypes() + * - getCookies() + */ + + List recipeList = new ArrayList<>(); + + // #getAcceptableLanguages() + recipeList.add(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() + recipeList.add(new RewriteMethodInvocation( + methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getDate()"), + (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("new Date()") + .imports("java.util.Date") + .build(); + addImport.accept("java.util.Date"); + 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)) + .withMethodType(m.getMethodType().withDeclaringType(TypeUtils.asFullyQualified(javaType))) + .withPrefix(Space.EMPTY) + )); + } + ) + ); + + // #getHeaderString(String) + recipeList.add(new RewriteMethodInvocation( + methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getHeaderString(java.lang.String)"), + (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("String.join(\", \", #{any(org.springframework.http.HttpHeaders)}.get(#{any(java.lang.String)})") + .build(); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #getLanguage() + recipeList.add(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() + recipeList.add(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() + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getMediaType()"), "getContentType", "org.springframework.http.HttpHeaders")); + + // #getRequestHeader(String) + recipeList.add(new RewriteMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.HttpHeaders getRequestHeader(java.lang.String)"), + (v, m, addImport) -> { + JavaTemplate template = JavaTemplate.builder("#{any(org.springframework.http.HttpHeaders)}.get(#{any(java.lang.String)})") + .build(); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + } + ) + ); + + // #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 JavaTemplate.builder("#{any(org.springframework.http.HttpHeaders)}").build().apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + } + ) + ); + + recipeList.add(new ChangeType("javax.ws.rs.core.HttpHeaders", "org.springframework.http.HttpHeaders", false)); + + 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 new file mode 100644 index 000000000..a5f16dd95 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapResponseWithResponseEntity.java @@ -0,0 +1,387 @@ +/* + * 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.NlsRewrite; +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.ArrayList; +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 { + + @Override + public List getRecipeList() { + List recipeList = new ArrayList<>(); + + recipeList.add(new SwapStatusForHttpStatus()); + // #status(int) + 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("ResponseEntity.status(" + args + ")") + .imports("org.springframework.http.ResponseEntity") + .build(); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + addImport.accept("org.springframework.http.ResponseEntity"); + return template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().toArray()); + })); + + // #status(int, String) + 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 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) + 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 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) + 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 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() + 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 template.apply(v.getCursor(), m.getCoordinates().replace()); + })); + + // #ok(Object) + 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("ResponseEntity.ok()") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + m = template.apply(v.getCursor(), m.getCoordinates().replace()); + markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); + } + return m; + })); + + // #ok(Object, MediaType) + 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("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 template.apply(v.getCursor(), m.getCoordinates().replace(), args.get(1)); + })); + + // #ok(Object, String) + 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("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 template.apply(v.getCursor(), m.getCoordinates().replace(), args.get(1)); + })); + + // #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 template.apply(v.getCursor(), m.getCoordinates().replace()); + })); + + // #accepted(Object) + 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("ResponseEntity.accepted()") + .imports("org.springframework.http.ResponseEntity") + .build(); + addImport.accept("org.springframework.http.ResponseEntity"); + v.maybeRemoveImport("javax.ws.rs.core.Response"); + m = template.apply(v.getCursor(), m.getCoordinates().replace()); + markTopLevelInvocationWithTemplate(v, m, args.get(0).print()); + return m; + })); + + // #created(URI) + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); + })); + + // #fromResponse(Response) + 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("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 template.apply(v.getCursor(), m.getCoordinates().replace(), e, e); + })); + + // #noContent() + // TODO: returns HeadersBuilder + 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 template.apply(v.getCursor(), m.getCoordinates().replace()); + })); + + // #notAcceptable(List { + 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 template.apply(v.getCursor(), m.getCoordinates().replace()); + })); + + // notModified(String) + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); + })); + + // notModified(EntityTag) - migration not supported + + // #seeOther(URI) + // TODO: Returns BodyBuilder + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); + })); + + // #serverError() + // Returns BodyBuilder + 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 template.apply(v.getCursor(), m.getCoordinates().replace()); + })); + + // #temporaryRedirect(URI) + // TODO: Returns BodyBuilder + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getArguments().get(0)); + })); + + // INSTANCE METHODS + + // #getAllowedMethods() + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + })); + + // #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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + })); + + // #getEntity() + 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 + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + })); + + // #getHeaderString(String) + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect(), m.getArguments().get(0)); + })); + + // #getMetadata() + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getMetadata()"), "getHeaders", "org.springframework.http.ResponseEntity")); + + // #getLanguage() + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + })); + + // #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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + })); + + // #getLength() + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + })); + + // #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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + })); + + // #getMediaType() + 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 template.apply(v.getCursor(), m.getCoordinates().replace(), m.getSelect()); + })); + + // #getStatus() + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStatus()"), "getStatusCodeValue", "org.springframework.http.ResponseEntity")); + + // #getStatusInfo() + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStatusInfo()"), "getStatusCode", "org.springframework.http.ResponseEntity")); + + // #getStringHeaders() + // TODO: different return type + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response getStringHeaders()"), "getHeaders", "org.springframework.http.ResponseEntity")); + + // #hasEntity() + recipeList.add(renameMethodInvocation(methodInvocationMatcher("javax.ws.rs.core.Response hasEntity()"), "hasBody", "org.springframework.http.ResponseEntity")); + + // #readEntity(..) + 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 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... + + // #getCookies() - not implemented + // #bufferEntity() - not implemented + // #close() - not implemented + // #getLink(String) - not implemented + // #getLinkBuilder(String) - not implemented + // #getLinks() - not implemented + // #hasLink() - not implemented + + + 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) { + 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"; + } + + @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 new file mode 100644 index 000000000..66b18784e --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxrs/recipes/SwapStatusForHttpStatus.java @@ -0,0 +1,131 @@ +/* + * 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.NlsRewrite; +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.ArrayList; +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.RewriteMethodInvocation.methodInvocationMatcher; + +public class SwapStatusForHttpStatus extends Recipe { + + @Override + public List getRecipeList() { + + List recipeList = new ArrayList<>(); + + // Switch JAX-RS Family to Spring HttpStatus.Series + recipeList.add(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) -> recipeList.add(new ReplaceConstantWithAnotherConstant("javax.ws.rs.core.Response$Status." + key,"org.springframework.http.HttpStatus." + value)) + ); + + // Instance methods + 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 + 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 + 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")); + })); + + // getReasonPhrase() doesn't need to be migrated - same named method returning the same type + + // Type reference replacement + + 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 + 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 new file mode 100644 index 000000000..e59f294ff --- /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.TreeVisitor; +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(); + TreeVisitor visitor = new AddOrUpdateAnnotationAttribute(targetAnnotationType, targetAttributeName, targetAttributeValue, false).getVisitor(); + if (targetAnnotationOnlyHasOneLiteralArgument(a)) { + a = (J.Annotation) visitor.visit(a, ctx, getCursor()); + } + return (J.Annotation) visitor.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..e6d005775 --- /dev/null +++ b/components/jaxrs-recipes/src/main/java/org/springframework/sbm/jee/jaxws/WebServiceDescriptor.java @@ -0,0 +1,296 @@ +/* + * 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), 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[] recipes = {new RemoveAnnotation(WEB_SERVICE_ANNOTATION), + new RemoveAnnotation(WEB_METHOD_ANNOTATION), + new RemoveAnnotation(WEB_PARAM_ANNOTATION), + new RemoveAnnotation(WEB_RESULT_ANNOTATION), + new RemoveAnnotation(ONE_WAY_ANNOTATION)}; + + typeAnnotatedAsWebService.apply(recipes); + + if (endpointInterface != null) { + endpointInterface.apply(recipes); + } + } + + 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/JaxRsThroughAdapterTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java new file mode 100644 index 000000000..4dc68a0ff --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/JaxRsThroughAdapterTest.java @@ -0,0 +1,341 @@ +/* + * 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.apache.commons.io.FileUtils; +import org.junit.jupiter.api.DisplayName; +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; + +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; +import static org.openrewrite.maven.Assertions.pomXml; + +/** + * @author Fabian Krüger + */ +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 + private JavaParser.Builder javaParserBuilder = JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true); + + @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 simple() { + 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; + + @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 + "\\""; + } + } + """, + //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.PathParam; + + + @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("", // this affects the resource getSourcePath() + 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") + 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 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; +// } +// +// } +// """ + + ), + pomXml( + POM_XML + ) + ) + ); + } + } + + @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()); + FileUtils.forceMkdir(to.toFile()); + FileUtils.copyDirectory(from.toFile(), to.toFile()); + + 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 +// 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() + .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()); + + 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/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..dffb7e001 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateEjbDeploymentDescriptorTest.java @@ -0,0 +1,249 @@ +/* + * 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; + 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) + .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" + + "@Local(" + LOCAL_EJB_INTERFACE + ".class)\n" + + "@Stateless(name = \"" + EJB_WITH_LOCAL_INTERFACE_NAME + "\")\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..703dd4b3c --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/ejb/actions/MigrateLocalStatelessSessionBeansTest.java @@ -0,0 +1,170 @@ +/* + * 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" + + "@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..5b7558e78 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/ConvertJaxRsAnnotationsTest.java @@ -0,0 +1,403 @@ +/* + * 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.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.java.api.Method; +import org.springframework.sbm.java.migration.conditions.HasTypeAnnotation; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConvertJaxRsAnnotationsTest { + + private final static String SPRING_VERSION = "5.3.13"; + + @Test + @Disabled("https://github.com/spring-projects-experimental/spring-boot-migrator/issues/897") + 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); + } + } + """; + + 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 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; + 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); + } + } + """; +// """ +// 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() throws Exception { + + 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")\s + 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")\s + 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() 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 = + """ + 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 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; + + @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() 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() + .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/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..292899543 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/actions/MigrateJaxRsAnnotations_Test.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.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 org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.RequestMethod; + + 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 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; + 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/BootifyJaxRsAnnotationsRecipeTest.java b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/BootifyJaxRsAnnotationsRecipeTest.java new file mode 100644 index 000000000..e69b46a84 --- /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..605199a01 --- /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.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 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..e3276147a --- /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 jakarta.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 jakarta.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..025f2dfc9 --- /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 jakarta.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 jakarta.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..cd1ec54d6 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ReplaceMediaTypeTest.java @@ -0,0 +1,559 @@ +/* + * 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.engine.recipe.AbstractAction; +import org.springframework.sbm.java.api.JavaSource; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; + +import static org.assertj.core.api.Assertions.assertThat; + +class ReplaceMediaTypeTest { + + private final static String SPRING_VERSION = "5.3.13"; + + final private AbstractAction action = new AbstractAction() { + @Override + public void apply(ProjectContext context) { + ReplaceMediaType r = new ReplaceMediaType(); + 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(); + 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 java.util.Map; + import org.springframework.http.MediaType; + + 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(); + 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..1d15264bc --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseBuilderTest.java @@ -0,0 +1,382 @@ +/* + * 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.openrewrite.SourceFile; +import org.springframework.rewrite.parser.RewriteExecutionContext; +import org.springframework.rewrite.parser.SpringRewriteProperties; +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.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; + +import java.util.stream.Stream; + +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" + + "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 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") + .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; + 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( + "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; + 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") + .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 + Stream parse = new RewriteJavaParser(new SpringRewriteProperties(), + new RewriteExecutionContext()).parse(actual); + + 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..8635eccb4 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseEntityReplacementTest.java @@ -0,0 +1,942 @@ +/* + * 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.engine.recipe.AbstractAction; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; + +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) { + context.getProjectJavaSources().apply(new SwapResponseWithResponseEntity()); + context.getProjectJavaSources().apply(new ReplaceMediaType()); + } + }; + + + @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" + + "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" + + "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" + + "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..709bdaebf --- /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.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 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..df94b89c7 --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jaxrs/recipes/ResponseStatusTest.java @@ -0,0 +1,417 @@ +/* + * 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.engine.recipe.AbstractAction; +import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.sbm.testhelper.common.utils.TestDiff; + +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(); + 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..ef63f50b7 --- /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.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 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..53e73cddc --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/jms/ReplaceMdbAnnotationWithJmsListenerTest.java @@ -0,0 +1,69 @@ +/* + * 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; + 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 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", + "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/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(", ")); + } + } +} 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/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..b3bb045be --- /dev/null +++ b/components/jaxrs-recipes/src/test/java/org/springframework/sbm/jee/tx/actions/MigrateJeeTransactionsToSpringBootActionTest.java @@ -0,0 +1,116 @@ +/* + * 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.*; + @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; + 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(); + + 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/jaxrs/bootify-jaxrs/given/pom.xml b/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml new file mode 100644 index 000000000..22d71ce93 --- /dev/null +++ b/components/jaxrs-recipes/testcode/jee/jaxrs/bootify-jaxrs/given/pom.xml @@ -0,0 +1,49 @@ + + + 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 + + + org.springframework.boot + spring-boot-starter-freemarker + 3.3.1 + + + + + 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/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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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, 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) { 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); } } 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( 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/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/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; 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-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) { 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) {