diff --git a/components/sbm-recipes-boot-upgrade/pom.xml b/components/sbm-recipes-boot-upgrade/pom.xml index 83025a12c..4a5bf03a2 100644 --- a/components/sbm-recipes-boot-upgrade/pom.xml +++ b/components/sbm-recipes-boot-upgrade/pom.xml @@ -63,6 +63,10 @@ spring-asciidoctor-backends ${spring-asciidoctor-backends.version} + + org.openrewrite.recipe + rewrite-spring + org.springframework.boot spring-boot-starter-test @@ -91,7 +95,6 @@ test - spring-release @@ -102,4 +105,4 @@ - \ No newline at end of file + diff --git a/components/sbm-recipes-boot-upgrade/src/main/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/BannerSupportHelper.java b/components/sbm-recipes-boot-upgrade/src/main/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/BannerSupportHelper.java index 82db3824a..24bdcc9b4 100644 --- a/components/sbm-recipes-boot-upgrade/src/main/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/BannerSupportHelper.java +++ b/components/sbm-recipes-boot-upgrade/src/main/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/BannerSupportHelper.java @@ -21,7 +21,6 @@ import org.springframework.sbm.project.resource.RewriteSourceFileHolder; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/components/sbm-recipes-boot-upgrade/src/main/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/ConstructorBindingHelper.java b/components/sbm-recipes-boot-upgrade/src/main/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/ConstructorBindingHelper.java new file mode 100644 index 000000000..56763321a --- /dev/null +++ b/components/sbm-recipes-boot-upgrade/src/main/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/ConstructorBindingHelper.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.sbm.boot.upgrade_27_30.report.helper; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.spring.boot3.RemoveConstructorBindingAnnotation; +import org.openrewrite.java.tree.J; +import org.springframework.sbm.boot.upgrade_27_30.report.SpringBootUpgradeReportSection; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.recipe.OpenRewriteSourceFilesFinder; +import org.springframework.sbm.project.resource.RewriteSourceFileHolder; +import org.springframework.sbm.support.openrewrite.GenericOpenRewriteRecipe; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + +public class ConstructorBindingHelper implements SpringBootUpgradeReportSection.Helper> { + + private List constructorBindingFiles; + + @Override + public String getDescription() { + return ""; + } + + @Override + public boolean evaluate(ProjectContext context) { + + GenericOpenRewriteRecipe> recipe = + new GenericOpenRewriteRecipe<>(() -> new UsesType("org.springframework.boot.context.properties.ConstructorBinding")); + + List> rewriteSourceFileHolders = + context.getProjectJavaSources().find(recipe); + + constructorBindingFiles = rewriteSourceFileHolders + .stream() + .map(k -> k.getAbsolutePath().toString()) + .collect(Collectors.toList()); + + return !rewriteSourceFileHolders.isEmpty(); + } + + @Override + public Map> getData(ProjectContext context) { + + return Map.of("files", constructorBindingFiles); + } +} diff --git a/components/sbm-recipes-boot-upgrade/src/main/resources/recipes/boot-new-report.yaml b/components/sbm-recipes-boot-upgrade/src/main/resources/recipes/boot-new-report.yaml index cf6030a66..2edafc9c1 100644 --- a/components/sbm-recipes-boot-upgrade/src/main/resources/recipes/boot-new-report.yaml +++ b/components/sbm-recipes-boot-upgrade/src/main/resources/recipes/boot-new-report.yaml @@ -130,6 +130,33 @@ recipe: boot-2.7-3.0-upgrade-report contributors: - "Sandeep Nagaraj[@sanagaraj-pivotal]" + + - title: Constructor Binding + helper: org.springframework.sbm.boot.upgrade_27_30.report.helper.ConstructorBindingHelper + change: |- + When using constructor bound @ConfigurationProperties the @ConstructorBinding annotation + is no longer required if the class has a single parameterized constructor. + If you have more than one constructor, you’ll still need to use `@ConstructorBinding` + to tell Spring Boot which one to use. + + For most users, this updated logic will allow for simpler `@ConfigurationProperties` + classes. If, however, you have a `@ConfigurationProperties` and you want to inject + beans into the constructor rather than binding it, you’ll now need to add an + `@Autowired` annotation. + affected: |- + We found usage of `@ConstructorBinding` in following files: + + <#list files as file> + * ${file} + + remediation: |- + Remove `@ConstructorBinding` if it matches the criteria, please refer issue: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/166[#166] + for more information + gitHubIssue: 166 + recipe: boot-2.7-3.0-upgrade-report + contributors: + - "Sandeep Nagaraj[@sanagaraj-pivotal]" + footer: |- We want to say thank you to all Contributors: diff --git a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/UpgradeBomTo30Test.java b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/UpgradeBomTo30Test.java index a689424a3..493f69054 100644 --- a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/UpgradeBomTo30Test.java +++ b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/UpgradeBomTo30Test.java @@ -20,8 +20,6 @@ import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.Result; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.tree.J; import org.openrewrite.maven.MavenParser; import org.openrewrite.maven.UpgradeDependencyVersion; import org.openrewrite.xml.tree.Xml; diff --git a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/ChangesToDataPropertiesReportTest.java b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/ChangesToDataPropertiesReportTest.java index 2afc33992..c5b05ebba 100644 --- a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/ChangesToDataPropertiesReportTest.java +++ b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/ChangesToDataPropertiesReportTest.java @@ -15,7 +15,6 @@ */ package org.springframework.sbm.boot.upgrade_27_30.report; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.sbm.boot.properties.SpringApplicationPropertiesPathMatcher; @@ -23,8 +22,6 @@ import org.springframework.sbm.engine.context.ProjectContext; import org.springframework.sbm.project.resource.TestProjectContext; -import java.nio.file.Path; -import java.util.Map; /** * @author Fabian Krüger @@ -44,26 +41,25 @@ void changesToDataPropertiesSection_renders() { .fromProjectContext(context) .shouldRenderAs( """ - === Changes to Data Properties - Issue: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/441[#441], Contributors: https://github.com/fabapp2[@fabapp2^, role="ext-link"] - - ==== What Changed - The data prefix has been reserved for Spring Data and any properties under the `data` prefix imply that Spring - Data is required on the classpath. - - ==== Why is the application affected - The scan found properties with `spring.data` prefix but no dependency matching `org.springframework.data:.*`. - - * file:///src/main/resources/application.properties[`src/main/resources/application.properties`] - ** `spring.data.foo` - * file:///src/main/resources/application-another.properties[`src/main/resources/application-another.properties`] - ** `spring.data.here` - - ==== Remediation - Either add `spring-data` dependency, rename the property or remove it in case it's not required anymore. - - """, - Map.of("PATH", Path.of(".").toAbsolutePath().resolve(TestProjectContext.getDefaultProjectRoot()).toString())); + === Changes to Data Properties + Issue: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/441[#441], Contributors: https://github.com/fabapp2[@fabapp2^, role="ext-link"] + + ==== What Changed + The data prefix has been reserved for Spring Data and any properties under the `data` prefix imply that Spring + Data is required on the classpath. + + ==== Why is the application affected + The scan found properties with `spring.data` prefix but no dependency matching `org.springframework.data:.*`. + + * file:///src/main/resources/application.properties[`src/main/resources/application.properties`] + ** `spring.data.foo` + * file:///src/main/resources/application-another.properties[`src/main/resources/application-another.properties`] + ** `spring.data.here` + + ==== Remediation + Either add `spring-data` dependency, rename the property or remove it in case it's not required anymore. + + """); } @Test diff --git a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/SpringBootUpgradeReportTestSupport.java b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/SpringBootUpgradeReportTestSupport.java index 26d1bb5c9..94208df93 100644 --- a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/SpringBootUpgradeReportTestSupport.java +++ b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/SpringBootUpgradeReportTestSupport.java @@ -21,6 +21,7 @@ import org.springframework.sbm.engine.recipe.Action; import org.springframework.sbm.engine.recipe.Recipe; import org.springframework.sbm.engine.recipe.Recipes; +import org.springframework.sbm.project.resource.TestProjectContext; import org.springframework.sbm.test.RecipeTestSupport; import org.springframework.sbm.testhelper.common.utils.TestDiff; import org.springframework.test.util.ReflectionTestUtils; @@ -76,7 +77,7 @@ public Assertion(BuilderData builderData) { public void shouldRenderAs(String expectedOutput) { - shouldRenderAs(expectedOutput, Map.of()); + shouldRenderAs(expectedOutput, defaultMap()); } public void shouldRenderAs(String expectedOutput, Map templateVariables) { @@ -90,7 +91,7 @@ public void shouldNotRender() { } public void shouldStartWith(String expectedOutput) { - shouldStartWith(expectedOutput, Map.of()); + shouldStartWith(expectedOutput, defaultMap()); } public void shouldStartWith(String expectedOutput, Map templateVariables) { @@ -99,6 +100,16 @@ public void shouldStartWith(String expectedOutput, Map templateV verify(assertion); } + + private Map defaultMap() { + String path = Path + .of(".") + .toAbsolutePath() + .resolve(TestProjectContext.getDefaultProjectRoot()).toString(); + + return Map.of("PATH", path); + } + private void verifyDoesNotRender() { if(SectionBuilderData.class.isInstance(builderData)) { SectionBuilderData sectionBuilderData = SectionBuilderData.class.cast(builderData); @@ -148,7 +159,6 @@ public void writeReport(String s, Path outputDir, String filename) { action.apply(reportBuilderData.getContext()); }); } else if(SectionBuilderData.class.isInstance(builderData)) { - SectionBuilderData sectionBuilderData = SectionBuilderData.class.cast(builderData); withRecipes(recipes -> { Recipe recipe = recipes.getRecipeByName("boot-2.7-3.0-upgrade-report2").get(); SpringBootUpgradeReportAction action = (SpringBootUpgradeReportAction) recipe.getActions().get(0); diff --git a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/BannerSupportHelperTest.java b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/BannerSupportHelperTest.java index cdf14ee4f..ca16ee9c2 100644 --- a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/BannerSupportHelperTest.java +++ b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/BannerSupportHelperTest.java @@ -21,8 +21,6 @@ import org.springframework.sbm.engine.context.ProjectContext; import org.springframework.sbm.project.resource.TestProjectContext; -import java.nio.file.Path; -import java.util.Map; public class BannerSupportHelperTest { @@ -55,7 +53,6 @@ public void rendersBannerSupportInformation() { ==== Remediation remove image banners and replace it with text-banner with banner.txt file - """, - Map.of("PATH", Path.of(".").toAbsolutePath().resolve(TestProjectContext.getDefaultProjectRoot()).toString())); + """); } } diff --git a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/ConstructorBindingHelperTest.java b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/ConstructorBindingHelperTest.java new file mode 100644 index 000000000..a6708db40 --- /dev/null +++ b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_27_30/report/helper/ConstructorBindingHelperTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021 - 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.sbm.boot.upgrade_27_30.report.helper; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; +import org.springframework.sbm.boot.upgrade_27_30.report.SpringBootUpgradeReportTestSupport; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.project.resource.TestProjectContext; + +public class ConstructorBindingHelperTest { + + @Test + public void reportMigrationSuggestionsWhenConstructorBindingUsageIsFound() { + @Language("java") + String javaClassWithConstructorBinding = """ + package com.example; + + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.boot.context.properties.ConfigurationProperties; + import org.springframework.boot.context.properties.ConstructorBinding; + + @ConfigurationProperties(prefix = "mail") + @ConstructorBinding + public class ConfigProperties { + private String hostName; + private int port; + private String from; + + public ConfigProperties(String hostName, int port, String from) { + this.hostName = hostName; + this.port = port; + this.from = from; + } + + public String getHostName() { + return hostName; + } + + public int getPort() { + return port; + } + + public String getFrom() { + return from; + } + } + """; + + ProjectContext context = TestProjectContext.buildProjectContext() + .addJavaSource("src/main/java", javaClassWithConstructorBinding) + .withBuildFileHavingDependencies("org.springframework.boot:spring-boot:2.7.1") + .build(); + + SpringBootUpgradeReportTestSupport + .generatedSection("Constructor Binding") + .fromProjectContext(context) + .shouldRenderAs( + """ + === Constructor Binding + Issue: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/166[#166], Contributors: https://github.com/sanagaraj-pivotal[@sanagaraj-pivotal^, role="ext-link"] + + ==== What Changed + When using constructor bound @ConfigurationProperties the @ConstructorBinding annotation + is no longer required if the class has a single parameterized constructor. + If you have more than one constructor, you’ll still need to use `@ConstructorBinding` + to tell Spring Boot which one to use. + + For most users, this updated logic will allow for simpler `@ConfigurationProperties` + classes. If, however, you have a `@ConfigurationProperties` and you want to inject + beans into the constructor rather than binding it, you’ll now need to add an + `@Autowired` annotation. + + ==== Why is the application affected + We found usage of `@ConstructorBinding` in following files: + + * /src/main/java/com/example/ConfigProperties.java + + ==== Remediation + Remove `@ConstructorBinding` if it matches the criteria, please refer issue: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/166[#166] + for more information + + """ + ); + } + + @Test + public void shouldNotReportConstructorBindingSuggestionWhenNothingIsFound() { + @Language("java") + String javaClassWithConstructorBinding = """ + package com.example; + + public class A { } + """; + + ProjectContext context = TestProjectContext.buildProjectContext() + .addJavaSource("src/main/java/com/example/A.java", javaClassWithConstructorBinding) + .withBuildFileHavingDependencies("org.springframework.boot:spring-boot:2.7.1") + .build(); + + SpringBootUpgradeReportTestSupport + .generatedSection("Constructor Binding") + .fromProjectContext(context) + .shouldNotRender(); + } +}