diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/FlowableDependsOnDatabaseInitializationDetector.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/FlowableDependsOnDatabaseInitializationDetector.java new file mode 100644 index 00000000000..83939e18fb4 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/FlowableDependsOnDatabaseInitializationDetector.java @@ -0,0 +1,46 @@ +/* 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 + * + * http://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.flowable.spring.boot; + +import java.util.Collections; +import java.util.Set; + +import org.flowable.common.engine.api.Engine; +import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDependsOnDatabaseInitializationDetector; +import org.springframework.core.env.Environment; + +/** + * {@link org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector} + * marking all Flowable {@link Engine} beans as depending on database initialization, so Spring + * Boot orders them after Flyway, Liquibase or spring.sql.init have run. + * + *

Can be opted out of by setting {@code flowable.depends-on-database-initialization-detection} + * to {@code false}, mirroring the JPA detector's opt-out behavior. + * + * @author Nikhil Bharadwaj Ramashasthri + */ +class FlowableDependsOnDatabaseInitializationDetector extends AbstractBeansOfTypeDependsOnDatabaseInitializationDetector { + + private final Environment environment; + + FlowableDependsOnDatabaseInitializationDetector(Environment environment) { + this.environment = environment; + } + + @Override + protected Set> getDependsOnDatabaseInitializationBeanTypes() { + boolean detectionEnabled = environment + .getProperty("flowable.depends-on-database-initialization-detection", Boolean.class, true); + return detectionEnabled ? Collections.singleton(Engine.class) : Collections.emptySet(); + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 050d700a1e2..6dbe5a34dd8 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -714,6 +714,12 @@ "name": "flowable.process.async-history.executor.timer-runnable-needed", "type": "java.lang.Boolean", "defaultValue": false + }, + { + "name": "flowable.depends-on-database-initialization-detection", + "type": "java.lang.Boolean", + "description": "Whether Flowable engine beans should be marked as depending on Spring Boot managed database initializers (Flyway, Liquibase, spring.sql.init), ensuring they start only after schema initialization has run.", + "defaultValue": true } ], "hints": [ @@ -761,7 +767,6 @@ "value": "spring_delegating_noop", "description": "Use the Spring Security 5 PasswordEncoderFactories.createDelegatingPasswordEncoder(). With NoopPasswordEncoder as the default password encoder for matches. This should be used for backwards compatibility with older versions." } - ] }, { @@ -797,4 +802,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 6fa3a2dcdb0..df3bcafce56 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,3 +1,5 @@ org.springframework.boot.EnvironmentPostProcessor=\ org.flowable.spring.boot.environment.FlowableDefaultPropertiesEnvironmentPostProcessor +org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\ + org.flowable.spring.boot.FlowableDependsOnDatabaseInitializationDetector diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java new file mode 100644 index 00000000000..3da7ff2e362 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java @@ -0,0 +1,77 @@ +/* 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 + * + * http://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.flowable.test.spring.boot; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.flowable.spring.boot.ProcessEngineAutoConfiguration; +import org.flowable.spring.boot.ProcessEngineServicesAutoConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +/** + * The Flowable engines create/validate their own schema on engine build, so their beans must be + * ordered after Spring Boot managed database initializers (Flyway, Liquibase, spring.sql.init). + * This is done through the {@code DependsOnDatabaseInitializationDetector} SPI (issue #4213). + * + * @author Nikhil Bharadwaj Ramashasthri + */ +class FlowableDependsOnDatabaseInitializationDetectorTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + DataSourceInitializationAutoConfiguration.class, + ProcessEngineAutoConfiguration.class, + ProcessEngineServicesAutoConfiguration.class)); + + @Test + void processEngineDependsOnDatabaseInitialization() { + contextRunner + .withPropertyValues( + "spring.sql.init.mode=always", + "flowable.check-process-definitions=false") + .run(context -> { + assertThat(context).hasNotFailed(); + String[] dependsOn = context.getBeanFactory() + .getBeanDefinition("processEngine") + .getDependsOn(); + assertThat(dependsOn) + .as("processEngine must wait for the SQL init database initializer") + .contains("dataSourceScriptDatabaseInitializer"); + }); + } + + @Test + void detectionCanBeOptedOut() { + contextRunner + .withPropertyValues( + "spring.sql.init.mode=always", + "flowable.check-process-definitions=false", + "flowable.depends-on-database-initialization-detection=false") + .run(context -> { + assertThat(context).hasNotFailed(); + String[] dependsOn = context.getBeanFactory() + .getBeanDefinition("processEngine") + .getDependsOn(); + assertThat(dependsOn) + .as("opt-out must remove the database initializer dependency") + .isNullOrEmpty(); + }); + } +}