diff --git a/src/main/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrdering.java b/src/main/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrderingUnconditionally.java similarity index 88% rename from src/main/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrdering.java rename to src/main/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrderingUnconditionally.java index 5518545fe..88491bf09 100644 --- a/src/main/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrdering.java +++ b/src/main/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrderingUnconditionally.java @@ -33,7 +33,7 @@ import java.util.Comparator; import java.util.List; -public class DatabaseComponentAndBeanInitializationOrdering extends Recipe { +public class DatabaseComponentAndBeanInitializationOrderingUnconditionally extends Recipe { private static final String JAVAX_SQL_DATA_SOURCE = "javax.sql.DataSource"; private static final List WELL_KNOW_DATA_SOURCE_TYPES = Arrays.asList( @@ -49,17 +49,18 @@ public class DatabaseComponentAndBeanInitializationOrdering extends Recipe { @Override public String getDisplayName() { - return "Adds `@DependsOnDatabaseInitialization` to Spring Beans and Components depending on `javax.sql.DataSource`"; + return "Unconditionally adds `@DependsOnDatabaseInitialization` to Spring Beans and Components depending on `javax.sql.DataSource`"; } @Override public String getDescription() { return "Beans of certain well-known types, such as `JdbcTemplate`, will be ordered so that they are initialized " + - "after the database has been initialized. If you have a bean that works with the `DataSource` directly, " + - "annotate its class or `@Bean` method with `@DependsOnDatabaseInitialization` to ensure that it too is " + - "initialized after the database has been initialized. See the " + - "[release notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.5-Release-Notes#initialization-ordering) " + - "for more."; + "after the database has been initialized. If you have a bean that works with the `DataSource` directly, " + + "annotate its class or `@Bean` method with `@DependsOnDatabaseInitialization` to ensure that it too is " + + "initialized after the database has been initialized. See the " + + "[release notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.5-Release-Notes#initialization-ordering) " + + "for more. This recipe will not check if the `@DependsOnDatabaseInitialization` annotation is on the classpath. " + + "This recipe is best combined with a precondition, as seen in `DatabaseComponentAndBeanInitializationOrdering`."; } @Override @@ -85,7 +86,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx); if (method.getMethodType() != null) { if (!isInitializationAnnoPresent(md.getLeadingAnnotations()) && isBean(md) && - requiresInitializationAnnotation(method.getMethodType().getReturnType())) { + requiresInitializationAnnotation(method.getMethodType().getReturnType())) { md = JavaTemplate.builder("@DependsOnDatabaseInitialization") .imports("org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization") .javaParser(JavaParser.fromJavaVersion() @@ -105,7 +106,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); if (!isInitializationAnnoPresent(cd.getLeadingAnnotations()) && isComponent(cd) && - requiresInitializationAnnotation(cd.getType())) { + requiresInitializationAnnotation(cd.getType())) { cd = JavaTemplate.builder("@DependsOnDatabaseInitialization") .imports("org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization") .javaParser(JavaParser.fromJavaVersion() diff --git a/src/main/resources/META-INF/rewrite/spring-boot-25.yml b/src/main/resources/META-INF/rewrite/spring-boot-25.yml index 977653564..0054ff67a 100644 --- a/src/main/resources/META-INF/rewrite/spring-boot-25.yml +++ b/src/main/resources/META-INF/rewrite/spring-boot-25.yml @@ -119,3 +119,19 @@ recipeList: artifactId: "*" newVersion: 2.2.0 overrideManagedVersion: true + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.spring.boot2.DatabaseComponentAndBeanInitializationOrdering +displayName: Adds `@DependsOnDatabaseInitialization` to Spring Beans and Components depending on `javax.sql.DataSource` +description: >- + Beans of certain well-known types, such as `JdbcTemplate`, will be ordered so that they are initialized after the database has been initialized. + If you have a bean that works with the `DataSource` directly, annotate its class or `@Bean` method with `@DependsOnDatabaseInitialization` to ensure that it too is initialized after the database has been initialized. + See the [release notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.5-Release-Notes#initialization-ordering) for more. +preconditions: + - org.openrewrite.java.dependencies.search.ModuleHasDependency: + groupIdPattern: org.springframework.boot + artifactIdPattern: spring-boot + version: '[2.5.0,)' +recipeList: + - org.openrewrite.java.spring.boot2.DatabaseComponentAndBeanInitializationOrderingUnconditionally diff --git a/src/testWithSpringBoot_2_4/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrderingTest.java b/src/testWithSpringBoot_2_4/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrderingTest.java index d3eb85047..06f53e02d 100644 --- a/src/testWithSpringBoot_2_4/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrderingTest.java +++ b/src/testWithSpringBoot_2_4/java/org/openrewrite/java/spring/boot2/DatabaseComponentAndBeanInitializationOrderingTest.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java.spring.boot2; +import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.java.JavaParser; @@ -22,12 +23,30 @@ import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.maven.Assertions.pomXml; class DatabaseComponentAndBeanInitializationOrderingTest implements RewriteTest { + private static final @Language("xml") String POM_WITH_SPRING_BOOT_25 = """ + + com.example + foo + 1.0.0 + + + org.springframework.boot + spring-boot + 2.5.1 + + + + """; + @Override public void defaults(RecipeSpec spec) { - spec.recipe(new DatabaseComponentAndBeanInitializationOrdering()) + spec + .recipeFromResources("org.openrewrite.java.spring.boot2.DatabaseComponentAndBeanInitializationOrdering") .parser(JavaParser.fromJavaVersion() .classpath("spring-beans", "spring-context", "spring-boot", "spring-jdbc", "spring-orm", "jooq", "persistence-api", "jaxb-api")); } @@ -35,377 +54,463 @@ public void defaults(RecipeSpec spec) { @DocumentExample @Test void dslContextBeanShouldNotBeAnnotated() { - //language=java rewriteRun( - java( - """ - import org.jooq.impl.DSL; - import org.jooq.DSLContext; - import org.jooq.SQLDialect; - import javax.sql.DataSource; - import org.springframework.context.annotation.Configuration; - import org.springframework.context.annotation.Bean; - - @Configuration - class PersistenceConfiguration { - - public static class A { private DataSource ds;} - - @Bean - DSLContext dslContext(DataSource ds) { - return DSL.using(ds, SQLDialect.SQLITE); - } - - @Bean - A a() { - return new A(); - } - } - """, - """ - import org.jooq.impl.DSL; - import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; - import org.jooq.DSLContext; - import org.jooq.SQLDialect; - import javax.sql.DataSource; - import org.springframework.context.annotation.Configuration; - import org.springframework.context.annotation.Bean; - - @Configuration - class PersistenceConfiguration { - - public static class A { private DataSource ds;} - - @Bean - DSLContext dslContext(DataSource ds) { - return DSL.using(ds, SQLDialect.SQLITE); - } - - @Bean - @DependsOnDatabaseInitialization - A a() { - return new A(); - } - } + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( + """ + import org.jooq.impl.DSL; + import org.jooq.DSLContext; + import org.jooq.SQLDialect; + import javax.sql.DataSource; + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + class PersistenceConfiguration { + + public static class A { private DataSource ds;} + + @Bean + DSLContext dslContext(DataSource ds) { + return DSL.using(ds, SQLDialect.SQLITE); + } + + @Bean + A a() { + return new A(); + } + } + """, """ + import org.jooq.impl.DSL; + import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; + import org.jooq.DSLContext; + import org.jooq.SQLDialect; + import javax.sql.DataSource; + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + class PersistenceConfiguration { + + public static class A { private DataSource ds;} + + @Bean + DSLContext dslContext(DataSource ds) { + return DSL.using(ds, SQLDialect.SQLITE); + } + + @Bean + @DependsOnDatabaseInitialization + A a() { + return new A(); + } + } + """ + ) ) ); } @Test void jdbcOperationsBeanShouldNotBeAnnotated() { - //language=java rewriteRun( - java( - """ - import javax.sql.DataSource; - import org.springframework.jdbc.core.JdbcOperations; - import org.springframework.context.annotation.Configuration; - import org.springframework.jdbc.core.JdbcTemplate; - import org.springframework.context.annotation.Bean; - - @Configuration - class PersistenceConfiguration { - - @Bean - JdbcOperations template(DataSource dataSource) { - return new JdbcTemplate(dataSource); - } - } + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( """ + import javax.sql.DataSource; + import org.springframework.jdbc.core.JdbcOperations; + import org.springframework.context.annotation.Configuration; + import org.springframework.jdbc.core.JdbcTemplate; + import org.springframework.context.annotation.Bean; + + @Configuration + class PersistenceConfiguration { + + @Bean + JdbcOperations template(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + } + """ + ) ) ); } @Test void namedParameterJdbcOperationsBeanShouldNotBeAnnotated() { - //language=java rewriteRun( - java( - """ - import javax.sql.DataSource; - import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; - import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; - import org.springframework.context.annotation.Configuration; - import org.springframework.context.annotation.Bean; - - @Configuration - class PersistenceConfiguration { - - @Bean - NamedParameterJdbcOperations template(DataSource dataSource) { - return new NamedParameterJdbcTemplate(dataSource); - } - } + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( """ + import javax.sql.DataSource; + import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; + import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + class PersistenceConfiguration { + + @Bean + NamedParameterJdbcOperations template(DataSource dataSource) { + return new NamedParameterJdbcTemplate(dataSource); + } + } + """ + ) ) ); } @Test void abstractEntityManagerFactoryBeanShouldNotBeAnnotated() { - //language=java rewriteRun( - java( - """ - import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; - import org.springframework.orm.jpa.LocalEntityManagerFactoryBean; - import org.springframework.context.annotation.Configuration; - import org.springframework.context.annotation.Bean; - - @Configuration - class PersistenceConfiguration { - - @Bean - AbstractEntityManagerFactoryBean entityManagerFactoryBean() { - return new LocalEntityManagerFactoryBean(); - } - } + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( """ + import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; + import org.springframework.orm.jpa.LocalEntityManagerFactoryBean; + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + class PersistenceConfiguration { + + @Bean + AbstractEntityManagerFactoryBean entityManagerFactoryBean() { + return new LocalEntityManagerFactoryBean(); + } + } + """ + ) ) ); } @Test void entityManagerFactoryBeanShouldNotBeAnnotated() { - //language=java rewriteRun( - java( - """ - import javax.persistence.Persistence; - import javax.persistence.EntityManagerFactory; - import org.springframework.context.annotation.Configuration; - import org.springframework.context.annotation.Bean; - - @Configuration - class PersistenceConfiguration { - - @Bean - EntityManagerFactory entityManagerFactory() { - return Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME"); - } - } + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( """ + import javax.persistence.Persistence; + import javax.persistence.EntityManagerFactory; + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + class PersistenceConfiguration { + + @Bean + EntityManagerFactory entityManagerFactory() { + return Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME"); + } + } + """ + ) ) ); } @Test void nonDataSourceBeanShouldNotBeAnnotated() { - //language=java rewriteRun( - java( - """ - public class MyService { - } + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( """ - ), - java( - """ - import org.springframework.context.annotation.Configuration; - import org.springframework.context.annotation.Bean; - - @Configuration - class MyThing { - - @Bean - MyService myService() { - return new MyService(); - } - } + public class MyService { + } + """ + ), + //language=java + java( """ + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + class MyThing { + + @Bean + MyService myService() { + return new MyService(); + } + } + """ + ) ) ); } @Test void beanDeclarationForBeanHavingMethodWithDataSourceParameter() { - //language=java rewriteRun( - java( - """ - import javax.sql.DataSource; - - public class CustomDataSourceInitializer { - public void initDatabase(DataSource ds, String sql) {} - } + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( """ - ), - java( - """ - import org.springframework.context.annotation.Configuration; - import org.springframework.context.annotation.Bean; - - @Configuration - public class MyAppDataConfig { - - @Bean - public CustomDataSourceInitializer customDataSourceInitializer() { - return new CustomDataSourceInitializer(); - } - } - """, - """ - import org.springframework.context.annotation.Configuration; - import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; - import org.springframework.context.annotation.Bean; - - @Configuration - public class MyAppDataConfig { - - @Bean - @DependsOnDatabaseInitialization - public CustomDataSourceInitializer customDataSourceInitializer() { - return new CustomDataSourceInitializer(); - } - } + import javax.sql.DataSource; + + public class CustomDataSourceInitializer { + public void initDatabase(DataSource ds, String sql) {} + } + """ + ), + //language=java + java( """ + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + public class MyAppDataConfig { + + @Bean + public CustomDataSourceInitializer customDataSourceInitializer() { + return new CustomDataSourceInitializer(); + } + } + """, + """ + import org.springframework.context.annotation.Configuration; + import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; + import org.springframework.context.annotation.Bean; + + @Configuration + public class MyAppDataConfig { + + @Bean + @DependsOnDatabaseInitialization + public CustomDataSourceInitializer customDataSourceInitializer() { + return new CustomDataSourceInitializer(); + } + } + """ + ) ) ); } @Test void beanDeclarationForBeanDependingOnDataSourceInConfiguration() { - //language=java rewriteRun( - java( - """ - import javax.sql.DataSource; + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( + """ + import javax.sql.DataSource; - public class CustomDataSourceInitializer { - private final DataSource ds; + public class CustomDataSourceInitializer { + private final DataSource ds; - CustomDataSourceInitializer(DataSource ds) { - this.ds = ds; - } + CustomDataSourceInitializer(DataSource ds) { + this.ds = ds; + } - public void initDatabase(String sql) {} - } + public void initDatabase(String sql) {} + } + """ + ), + //language=java + java( """ - ), - java( - """ - import javax.sql.DataSource; - import org.springframework.context.annotation.Configuration; - import org.springframework.context.annotation.Bean; - - @Configuration - public class MyAppDataConfig { - - @Bean - public CustomDataSourceInitializer customDataSourceInitializer(DataSource ds) { - return new CustomDataSourceInitializer(ds); - } - } - """, - """ - import javax.sql.DataSource; - import org.springframework.context.annotation.Configuration; - import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; - import org.springframework.context.annotation.Bean; - - @Configuration - public class MyAppDataConfig { - - @Bean - @DependsOnDatabaseInitialization - public CustomDataSourceInitializer customDataSourceInitializer(DataSource ds) { - return new CustomDataSourceInitializer(ds); - } - } + import javax.sql.DataSource; + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + public class MyAppDataConfig { + + @Bean + public CustomDataSourceInitializer customDataSourceInitializer(DataSource ds) { + return new CustomDataSourceInitializer(ds); + } + } + """, """ + import javax.sql.DataSource; + import org.springframework.context.annotation.Configuration; + import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; + import org.springframework.context.annotation.Bean; + + @Configuration + public class MyAppDataConfig { + + @Bean + @DependsOnDatabaseInitialization + public CustomDataSourceInitializer customDataSourceInitializer(DataSource ds) { + return new CustomDataSourceInitializer(ds); + } + } + """ + ) ) ); } @Test void beanDeclarationForThirdPartyDataSourceInitialization() { - //language=java rewriteRun( - java( - """ - package com.db.magic; + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( + """ + package com.db.magic; - import org.springframework.beans.factory.annotation.Autowired; - import javax.sql.DataSource; + import org.springframework.beans.factory.annotation.Autowired; + import javax.sql.DataSource; - public class MagicDataSourceInitializer { - @Autowired - private final DataSource ds; + public class MagicDataSourceInitializer { + @Autowired + private final DataSource ds; - public void initDatabase(String sql) {} - } + public void initDatabase(String sql) {} + } + """ + ), + //language=java + java( """ - ), - java( - """ - package com.my.dbinit; - - import com.db.magic.MagicDataSourceInitializer; - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - - @Configuration - public class MyMagicDataConfig { - - @Bean - public MagicDataSourceInitializer magicDataSourceInitializer() { - return new MagicDataSourceInitializer(); - } - } - """, - """ - package com.my.dbinit; - - import com.db.magic.MagicDataSourceInitializer; - import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - - @Configuration - public class MyMagicDataConfig { - - @Bean - @DependsOnDatabaseInitialization - public MagicDataSourceInitializer magicDataSourceInitializer() { - return new MagicDataSourceInitializer(); - } - } + package com.my.dbinit; + + import com.db.magic.MagicDataSourceInitializer; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class MyMagicDataConfig { + + @Bean + public MagicDataSourceInitializer magicDataSourceInitializer() { + return new MagicDataSourceInitializer(); + } + } + """, """ + package com.my.dbinit; + + import com.db.magic.MagicDataSourceInitializer; + import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class MyMagicDataConfig { + + @Bean + @DependsOnDatabaseInitialization + public MagicDataSourceInitializer magicDataSourceInitializer() { + return new MagicDataSourceInitializer(); + } + } + """ + ) ) ); } @Test void componentDoesDdl() { - //language=java rewriteRun( - java( - """ - import javax.sql.DataSource; - import org.springframework.stereotype.Component; - - @Component - public class MyDbInitializerComponent { - - public void initSchema(DataSource ds) { - } - } - """, - """ - import javax.sql.DataSource; - - import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; - import org.springframework.stereotype.Component; - - @Component - @DependsOnDatabaseInitialization - public class MyDbInitializerComponent { - - public void initSchema(DataSource ds) { - } - } + mavenProject("project-maven", + pomXml(POM_WITH_SPRING_BOOT_25), + //language=java + java( + """ + import javax.sql.DataSource; + import org.springframework.stereotype.Component; + + @Component + public class MyDbInitializerComponent { + + public void initSchema(DataSource ds) { + } + } + """, + """ + import javax.sql.DataSource; + + import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; + import org.springframework.stereotype.Component; + + @Component + @DependsOnDatabaseInitialization + public class MyDbInitializerComponent { + + public void initSchema(DataSource ds) { + } + } + """ + ) + ) + ); + } + + @Test + void dontAnnotateNonBootModules() { + rewriteRun( + mavenProject("project-maven", + //language=xml + pomXml( + """ + + com.example + foo + 1.0.0 + + + org.springframework + spring-context + 5.3.1 + + + + """ + ), + //language=java + java( """ + import org.jooq.impl.DSL; + import org.jooq.DSLContext; + import org.jooq.SQLDialect; + import javax.sql.DataSource; + import org.springframework.context.annotation.Configuration; + import org.springframework.context.annotation.Bean; + + @Configuration + class PersistenceConfiguration { + + public static class A { private DataSource ds;} + + @Bean + DSLContext dslContext(DataSource ds) { + return DSL.using(ds, SQLDialect.SQLITE); + } + + @Bean + A a() { + return new A(); + } + } + """ + ) ) ); }