diff --git a/.github/workflows/graal-native.yml b/.github/workflows/graal-native.yml
new file mode 100644
index 00000000000..82ffd221e6e
--- /dev/null
+++ b/.github/workflows/graal-native.yml
@@ -0,0 +1,33 @@
+name: Flowable Graal Build
+
+on:
+ push:
+ branches:
+ - main
+ - 'flowable-release-*'
+
+env:
+ MAVEN_ARGS: >-
+ -B -V --no-transfer-progress
+ -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
+
+jobs:
+ test_graal:
+ name: Linux Graal Native
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: graalvm/setup-graalvm@v1
+ with:
+ java-version: 17
+ distribution: graalvm
+ - name: Cache Maven Repository
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+ restore-keys: ${{ runner.os }}-m2
+ - name: Install
+ run: ./mvnw install -Pdistro ${MAVEN_ARGS} -DskipTests=true -Dmaven.javadoc.skip=true
+ - name: Test
+ run: ./mvnw test -PnativeTest,native,distro,errorLogging ${MAVEN_ARGS} -Dmaven.test.redirectTestOutputToFile=false -pl modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native
diff --git a/modules/flowable-app-engine/src/main/java/org/flowable/app/engine/impl/aot/FlowableAppRuntimeHints.java b/modules/flowable-app-engine/src/main/java/org/flowable/app/engine/impl/aot/FlowableAppRuntimeHints.java
new file mode 100644
index 00000000000..76235896c78
--- /dev/null
+++ b/modules/flowable-app-engine/src/main/java/org/flowable/app/engine/impl/aot/FlowableAppRuntimeHints.java
@@ -0,0 +1,31 @@
+/* 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.app.engine.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableAppRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ resourceHints.registerPattern("org/flowable/app/db/liquibase/flowable-app-db-changelog.xml");
+ FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/app/db/mapping", hints, classLoader);
+ }
+}
diff --git a/modules/flowable-app-engine/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-app-engine/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..4c43ba98c20
--- /dev/null
+++ b/modules/flowable-app-engine/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.app.engine.impl.aot.FlowableAppRuntimeHints
diff --git a/modules/flowable-batch-service/pom.xml b/modules/flowable-batch-service/pom.xml
index 170e9a3ced7..b4e5aac0ced 100755
--- a/modules/flowable-batch-service/pom.xml
+++ b/modules/flowable-batch-service/pom.xml
@@ -89,6 +89,9 @@
org.flowable.batch.service.db.mapping.entity
+
+ org.springframework*;resolution:=optional
+
diff --git a/modules/flowable-batch-service/src/main/java/org/flowable/batch/service/impl/aot/FlowableBatchServiceRuntimeHints.java b/modules/flowable-batch-service/src/main/java/org/flowable/batch/service/impl/aot/FlowableBatchServiceRuntimeHints.java
new file mode 100644
index 00000000000..baba6d34262
--- /dev/null
+++ b/modules/flowable-batch-service/src/main/java/org/flowable/batch/service/impl/aot/FlowableBatchServiceRuntimeHints.java
@@ -0,0 +1,31 @@
+/* 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.batch.service.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableBatchServiceRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/batch/service/db", resourceHints);
+
+ }
+}
diff --git a/modules/flowable-batch-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-batch-service/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..2a04bc04f27
--- /dev/null
+++ b/modules/flowable-batch-service/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.batch.service.impl.aot.FlowableBatchServiceRuntimeHints
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java
index c5ef2fec146..737b716b092 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java
@@ -93,10 +93,12 @@ public void run() {
if (changeTypeValue.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_ALL.equals(changeTypeValue) ||
(VariableListenerEventDefinition.CHANGE_TYPE_UPDATE_CREATE.equals(changeTypeValue) &&
(VariableListenerEventDefinition.CHANGE_TYPE_CREATE.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_UPDATE.equals(variableListenerData.getChangeType())))) {
-
- itVariableListener.remove();
- CommandContextUtil.getAgenda().planTriggerPlanItemInstanceOperation(planItemInstance);
- triggeredPlanItemInstance = true;
+
+ if (!variableListenerData.containsProcessedElementId(planItemInstance.getPlanItemDefinitionId())) {
+ CommandContextUtil.getAgenda().planTriggerPlanItemInstanceOperation(planItemInstance);
+ triggeredPlanItemInstance = true;
+ variableListenerData.addProcessedElementId(planItemInstance.getPlanItemDefinitionId());
+ }
}
}
}
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/aot/FlowableCmmnRuntimeHints.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/aot/FlowableCmmnRuntimeHints.java
new file mode 100644
index 00000000000..60821021edd
--- /dev/null
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/aot/FlowableCmmnRuntimeHints.java
@@ -0,0 +1,38 @@
+/* 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.cmmn.engine.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar;
+import org.flowable.variable.service.impl.QueryVariableValue;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableCmmnRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/cmmn/db/mapping", hints, classLoader);
+ resourceHints.registerPattern("org/flowable/cmmn/db/liquibase/flowable-cmmn-db-changelog.xml");
+ resourceHints.registerPattern("org/flowable/cmmn/db/liquibase/flowable-cmmn-db-changelog-crdb.xml");
+ resourceHints.registerPattern("org/flowable/impl/cmmn/parser/*.xsd");
+
+ hints.reflection()
+ .registerType(QueryVariableValue.class, MemberCategory.INVOKE_PUBLIC_METHODS);
+ }
+}
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java
index e3d59151aff..299b6f6b44e 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java
@@ -20,6 +20,7 @@
import org.flowable.cmmn.model.CmmnModel;
import org.flowable.common.engine.api.repository.EngineDeployment;
import org.flowable.common.engine.impl.util.IoUtil;
+import org.flowable.common.engine.impl.util.NativeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -74,11 +75,15 @@ protected CmmnResourceEntity createResourceEntity() {
}
public boolean shouldCreateDiagram(CaseDefinitionEntity caseDefinition, EngineDeployment deployment) {
+ if (NativeUtil.inNativeImage()) {
+ // Do not create diagram in native image
+ return false;
+ }
if (deployment.isNew() && caseDefinition.hasGraphicalNotation()
&& CommandContextUtil.getCmmnEngineConfiguration().isCreateDiagramOnDeploy()) {
// If the 'getProcessDiagramResourceNameFromDeployment' call returns null, it means
- // no diagram image for the process definition was provided in the deployment resources.
+ // no diagram image for the case definition was provided in the deployment resources.
return ResourceNameUtil.getCaseDiagramResourceNameFromDeployment(caseDefinition, deployment.getResources()) == null;
}
diff --git a/modules/flowable-cmmn-engine/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-cmmn-engine/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..092b938353d
--- /dev/null
+++ b/modules/flowable-cmmn-engine/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.cmmn.engine.impl.aot.FlowableCmmnRuntimeHints
diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java
index 1d08e3b7f26..ce4761f5605 100644
--- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java
+++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java
@@ -196,6 +196,74 @@ public void testTriggerVariableEventListenerInStageOnlyCreate() {
assertCaseInstanceEnded(caseInstance);
}
+
+ @Test
+ @CmmnDeployment
+ public void testTriggerMultipleVariableEventListeners() {
+ CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("variableListener").start();
+
+ // 5 plan items reachable
+ assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().count()).isEqualTo(5);
+ assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().list())
+ .extracting(PlanItemInstance::getPlanItemDefinitionType, PlanItemInstance::getPlanItemDefinitionId, PlanItemInstance::getState)
+ .containsExactlyInAnyOrder(
+ tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.AVAILABLE),
+ tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.AVAILABLE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.AVAILABLE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.AVAILABLE)
+ );
+
+ if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) {
+ assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().list())
+ .extracting(HistoricPlanItemInstance::getPlanItemDefinitionType, HistoricPlanItemInstance::getPlanItemDefinitionId, HistoricPlanItemInstance::getState)
+ .containsExactlyInAnyOrder(
+ tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.AVAILABLE),
+ tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.AVAILABLE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.AVAILABLE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.AVAILABLE)
+ );
+ }
+
+ // create different variable
+ cmmnRuntimeService.setVariable(caseInstance.getId(), "var2", "test");
+
+ assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionType(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER).count()).isEqualTo(2);
+
+ // create var1 variable to trigger variable event listener
+ cmmnRuntimeService.setVariable(caseInstance.getId(), "var1", "test");
+
+ // variable event listener should be completed
+ assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionType(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER).count()).isZero();
+ assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().list())
+ .extracting(PlanItemInstance::getPlanItemDefinitionType, PlanItemInstance::getPlanItemDefinitionId, PlanItemInstance::getState)
+ .containsExactlyInAnyOrder(
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.ACTIVE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.ACTIVE)
+ );
+
+ assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionId("taskB").planItemInstanceStateActive().count()).isEqualTo(1);
+ assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionId("taskC").planItemInstanceStateActive().count()).isEqualTo(1);
+
+ if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) {
+ assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().list())
+ .extracting(HistoricPlanItemInstance::getPlanItemDefinitionType, HistoricPlanItemInstance::getPlanItemDefinitionId, HistoricPlanItemInstance::getState)
+ .containsExactlyInAnyOrder(
+ tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.COMPLETED),
+ tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.COMPLETED),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.ACTIVE),
+ tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.ACTIVE)
+ );
+ }
+
+
+ assertCaseInstanceNotEnded(caseInstance);
+ cmmnTaskService.createTaskQuery().list().forEach(t -> cmmnTaskService.complete(t.getId()));
+ assertCaseInstanceEnded(caseInstance);
+ }
@Test
@CmmnDeployment
diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn
new file mode 100644
index 00000000000..8b1390ca4cc
--- /dev/null
+++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ occur
+
+
+
+
+ occur
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/aot/FlowableDmnRuntimeHints.java b/modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/aot/FlowableDmnRuntimeHints.java
new file mode 100644
index 00000000000..f3c0e982531
--- /dev/null
+++ b/modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/aot/FlowableDmnRuntimeHints.java
@@ -0,0 +1,32 @@
+/* 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.dmn.engine.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableDmnRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/dmn/db/mapping", hints, classLoader);
+ resourceHints.registerPattern("org/flowable/dmn/db/liquibase/flowable-dmn-db-changelog.xml");
+ resourceHints.registerPattern("org/flowable/impl/dmn/parser/*.xsd");
+ }
+}
diff --git a/modules/flowable-dmn-engine/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-dmn-engine/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..50cfebdb05a
--- /dev/null
+++ b/modules/flowable-dmn-engine/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.dmn.engine.impl.aot.FlowableDmnRuntimeHints
diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableCommonRuntimeHints.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableCommonRuntimeHints.java
new file mode 100644
index 00000000000..05eb7abefe1
--- /dev/null
+++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableCommonRuntimeHints.java
@@ -0,0 +1,39 @@
+/* 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.common.engine.impl.aot;
+
+import org.flowable.common.engine.impl.persistence.entity.ByteArrayRefTypeHandler;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableCommonRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ resourceHints.registerPattern("META-INF/services/liquibase.hub.HubService");
+ resourceHints.registerPattern("META-INF/services/liquibase.license.LicenseService");
+ resourceHints.registerResourceBundle("org.flowable.common.engine.impl.de.odysseus.el.misc.LocalStrings");
+ // If we can detect which DB is being used we can perhaps register only the appropriate DB file
+ resourceHints.registerPattern("org/flowable/common/db/properties/*.properties");
+ FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/common/db", resourceHints);
+
+ hints.reflection()
+ .registerType(ByteArrayRefTypeHandler.class, MemberCategory.values());
+ }
+}
diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisResourceHintsRegistrar.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisResourceHintsRegistrar.java
new file mode 100644
index 00000000000..9c63ad0f8ef
--- /dev/null
+++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisResourceHintsRegistrar.java
@@ -0,0 +1,106 @@
+/* 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.common.engine.impl.aot;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
+import org.apache.ibatis.parsing.XNode;
+import org.apache.ibatis.parsing.XPathParser;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ReflectionHints;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.TypeReference;
+import org.springframework.core.io.ClassPathResource;
+
+/**
+ * Register the necessary resource hints for the Flowable SQL resources.
+ *
+ * @author Filip Hrisafov
+ */
+public class FlowableMyBatisResourceHintsRegistrar {
+
+ public static void registerMappingResources(String baseFolder, RuntimeHints runtimeHints, ClassLoader classLoader) {
+ ResourceHints resourceHints = runtimeHints.resources();
+ String mappingsPath = baseFolder + "/mappings.xml";
+ ClassPathResource mappingsResource = new ClassPathResource(mappingsPath);
+ resourceHints.registerResource(mappingsResource);
+ try (InputStream mappingsStream = mappingsResource.getInputStream()) {
+ XPathParser parser = createParser(mappingsStream);
+
+ List mappers = parser.evalNodes("/configuration/mappers/mapper");
+ for (XNode mapper : mappers) {
+ registerMapper(mapper.getStringAttribute("resource"), runtimeHints, classLoader);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to read mappings " + mappingsPath, e);
+ }
+ }
+
+ public static void registerMapper(String mapperPath, RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ ClassPathResource mapperResource = new ClassPathResource(mapperPath);
+ resourceHints.registerResource(mapperResource);
+
+ ReflectionHints reflectionHints = hints.reflection();
+ MemberCategory[] memberCategories = MemberCategory.values();
+ try (InputStream mapperStream = mapperResource.getInputStream()) {
+ XPathParser parser = createParser(mapperStream);
+ XNode mapper = parser.evalNode("/mapper");
+ // The xpath resolving is similar like what MyBatis does in XMLMapperBuilder#parse
+ for (XNode resultMap : mapper.evalNodes("/mapper/resultMap")) {
+ String type = resultMap.getStringAttribute("type");
+ if (type != null) {
+ reflectionHints.registerType(TypeReference.of(type), memberCategories);
+ }
+ }
+
+ for (XNode statement : mapper.evalNodes("select|insert|update|delete")) {
+ String parameterType = statement.getStringAttribute("parameterType");
+ if (parameterType != null) {
+ if (parameterType.startsWith("org.flowable") || parameterType.startsWith("java.")) {
+ reflectionHints.registerType(TypeReference.of(parameterType), memberCategories);
+ } else if (parameterType.equals("map")) {
+ reflectionHints.registerType(Map.class, memberCategories);
+ }
+ }
+
+ String resultType = statement.getStringAttribute("resultType");
+ if (resultType != null) {
+ if (resultType.equals("long")) {
+ reflectionHints.registerType(long.class, memberCategories);
+ reflectionHints.registerType(Long.class, memberCategories);
+ } else if (resultType.equals("string")) {
+ reflectionHints.registerType(String.class, memberCategories);
+ } else if (resultType.equals("map")) {
+ reflectionHints.registerType(HashMap.class, memberCategories);
+ }
+ }
+ }
+
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to read mapper from " + mapperPath, e);
+ }
+ }
+
+ protected static XPathParser createParser(InputStream stream) {
+ return new XPathParser(stream, false, null, new XMLMapperEntityResolver());
+ }
+
+}
diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisRuntimeHints.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisRuntimeHints.java
new file mode 100644
index 00000000000..06ad1a82183
--- /dev/null
+++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisRuntimeHints.java
@@ -0,0 +1,73 @@
+/* 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.common.engine.impl.aot;
+
+import org.apache.ibatis.cache.decorators.FifoCache;
+import org.apache.ibatis.cache.decorators.LruCache;
+import org.apache.ibatis.cache.decorators.SoftCache;
+import org.apache.ibatis.cache.decorators.WeakCache;
+import org.apache.ibatis.cache.impl.PerpetualCache;
+import org.apache.ibatis.javassist.util.proxy.ProxyFactory;
+import org.apache.ibatis.javassist.util.proxy.RuntimeSupport;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl;
+import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl;
+import org.apache.ibatis.logging.log4j2.Log4j2Impl;
+import org.apache.ibatis.logging.nologging.NoLoggingImpl;
+import org.apache.ibatis.logging.slf4j.Slf4jImpl;
+import org.apache.ibatis.logging.stdout.StdOutImpl;
+import org.apache.ibatis.scripting.defaults.RawLanguageDriver;
+import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ReflectionHints;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableMyBatisRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ // These hints are coming from https://github.com/mybatis/spring-boot-starter/wiki/MyBatisNativeConfiguration.java
+ MemberCategory[] memberCategories = MemberCategory.values();
+ ReflectionHints reflectionHints = hints.reflection();
+ reflectionHints.registerType(Configuration.class, memberCategories);
+ reflectionHints.registerType(RawLanguageDriver.class, memberCategories);
+ reflectionHints.registerType(XMLLanguageDriver.class, memberCategories);
+ reflectionHints.registerType(RuntimeSupport.class, memberCategories);
+ reflectionHints.registerType(ProxyFactory.class, memberCategories);
+ reflectionHints.registerType(Slf4jImpl.class, memberCategories);
+ reflectionHints.registerType(Log.class, memberCategories);
+ reflectionHints.registerType(JakartaCommonsLoggingImpl.class, memberCategories);
+ reflectionHints.registerType(Log4j2Impl.class, memberCategories);
+ reflectionHints.registerType(Jdk14LoggingImpl.class, memberCategories);
+ reflectionHints.registerType(StdOutImpl.class, memberCategories);
+ reflectionHints.registerType(NoLoggingImpl.class, memberCategories);
+ reflectionHints.registerType(SqlSessionFactory.class, memberCategories);
+ reflectionHints.registerType(PerpetualCache.class, memberCategories);
+ reflectionHints.registerType(FifoCache.class, memberCategories);
+ reflectionHints.registerType(LruCache.class, memberCategories);
+ reflectionHints.registerType(SoftCache.class, memberCategories);
+ reflectionHints.registerType(WeakCache.class, memberCategories);
+
+ ResourceHints resourceHints = hints.resources();
+ resourceHints.registerPattern("org/apache/ibatis/builder/xml/*.dtd");
+ resourceHints.registerPattern("org/apache/ibatis/builder/xml/*.xsd");
+
+ }
+}
diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableSqlResourceHintsRegistrar.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableSqlResourceHintsRegistrar.java
new file mode 100644
index 00000000000..9fbf3c0f952
--- /dev/null
+++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableSqlResourceHintsRegistrar.java
@@ -0,0 +1,31 @@
+/* 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.common.engine.impl.aot;
+
+import java.util.stream.Stream;
+
+import org.springframework.aot.hint.ResourceHints;
+
+/**
+ * Register the necessary resource hints for the Flowable SQL resources.
+ *
+ * @author Filip Hrisafov
+ */
+public class FlowableSqlResourceHintsRegistrar {
+
+ public static void registerSqlResources(String baseFolder, ResourceHints resourceHints) {
+ Stream.of("create", "drop", "upgrade")
+ .forEach(folder -> resourceHints.registerPattern(baseFolder + "/" + folder + "/*.sql"));
+ }
+
+}
diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/NativeUtil.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/NativeUtil.java
new file mode 100644
index 00000000000..4c206628fb3
--- /dev/null
+++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/NativeUtil.java
@@ -0,0 +1,37 @@
+/* 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.common.engine.impl.util;
+
+/**
+ * A util for detecting a GraalVM native environment.
+ * This is inspired by the Spring {@code NativeDetector}
+ *
+ * @author Filip Hrisafov
+ */
+public class NativeUtil {
+
+ // See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java
+ private static final String imageCode = System.getProperty("org.graalvm.nativeimage.imagecode");
+
+ private static final boolean inNativeImage = (imageCode != null);
+
+ /**
+ * Returns {@code true} if running in a native image context (for example
+ * {@code buildtime}, {@code runtime}, or {@code agent}) expressed by setting the
+ * {@code org.graalvm.nativeimage.imagecode} system property to any value.
+ */
+ public static boolean inNativeImage() {
+ return inNativeImage;
+ }
+
+}
diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java
index d21d3d5e916..99049a3af51 100644
--- a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java
+++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java
@@ -12,6 +12,9 @@
*/
package org.flowable.common.engine.impl.variablelistener;
+import java.util.ArrayList;
+import java.util.List;
+
public class VariableListenerSessionData {
public static final String VARIABLE_CREATE = "create";
@@ -22,6 +25,7 @@ public class VariableListenerSessionData {
protected String scopeId;
protected String scopeType;
protected String scopeDefinitionId;
+ protected List processedElementIds = new ArrayList<>();
public VariableListenerSessionData(String changeType, String scopeId, String scopeType, String scopeDefinitionId) {
this.changeType = changeType;
@@ -57,5 +61,11 @@ public String getScopeDefinitionId() {
}
public void setScopeDefinitionId(String scopeDefinitionId) {
this.scopeDefinitionId = scopeDefinitionId;
- }
+ }
+ public boolean containsProcessedElementId(String elementId) {
+ return this.processedElementIds.contains(elementId);
+ }
+ public void addProcessedElementId(String elementId) {
+ this.processedElementIds.add(elementId);
+ }
}
diff --git a/modules/flowable-engine-common/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-engine-common/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..019e315d926
--- /dev/null
+++ b/modules/flowable-engine-common/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,3 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.common.engine.impl.aot.FlowableCommonRuntimeHints,\
+org.flowable.common.engine.impl.aot.FlowableMyBatisRuntimeHints
diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java
index 8691987d497..ac6e2bce53a 100644
--- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java
+++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java
@@ -106,8 +106,10 @@ public void run() {
VariableListenerEventDefinition.CHANGE_TYPE_ALL.equals(changeTypeValue) || (VariableListenerEventDefinition.CHANGE_TYPE_UPDATE_CREATE.equals(changeTypeValue) &&
(VariableListenerEventDefinition.CHANGE_TYPE_CREATE.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_UPDATE.equals(variableListenerData.getChangeType())))) {
- itVariableListener.remove();
- CommandContextUtil.getAgenda().planTriggerExecutionOperation(execution);
+ if (!variableListenerData.containsProcessedElementId(execution.getActivityId())) {
+ CommandContextUtil.getAgenda().planTriggerExecutionOperation(execution);
+ variableListenerData.addProcessedElementId(execution.getActivityId());
+ }
}
}
}
diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/aot/FlowableProcessRuntimeHints.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/aot/FlowableProcessRuntimeHints.java
new file mode 100644
index 00000000000..3c3f730eedc
--- /dev/null
+++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/aot/FlowableProcessRuntimeHints.java
@@ -0,0 +1,39 @@
+/* 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.engine.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar;
+import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar;
+import org.flowable.variable.service.impl.QueryVariableValue;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableProcessRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ resourceHints.registerPattern("META-INF/services/javax.script.ScriptEngineFactory");
+ FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/db", resourceHints);
+ FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/db/mapping", hints, classLoader);
+ resourceHints.registerPattern("org/flowable/impl/bpmn/parser/*.xsd");
+
+ hints.reflection()
+ .registerType(QueryVariableValue.class, MemberCategory.INVOKE_PUBLIC_METHODS);
+ }
+}
diff --git a/modules/flowable-engine/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-engine/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..280667c77f0
--- /dev/null
+++ b/modules/flowable-engine/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.engine.impl.aot.FlowableProcessRuntimeHints
diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java
index 6291fc4fb72..15661b7efc0 100644
--- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java
+++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java
@@ -88,6 +88,28 @@ public void catchVariableListenerUpdate() {
assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(0);
}
+ @Test
+ @Deployment
+ public void multipleCatchVariableListeners() {
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("catchVariableListener");
+
+ assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(2);
+
+ assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(3);
+
+ runtimeService.setVariable(processInstance.getId(), "var2", "test");
+
+ assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(2);
+
+ runtimeService.setVariable(processInstance.getId(), "var1", "test");
+
+ assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(0);
+
+ assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(2);
+ assertThat(taskService.createTaskQuery().taskDefinitionKey("aftertask").processInstanceId(processInstance.getId()).count()).isEqualTo(1);
+ assertThat(taskService.createTaskQuery().taskDefinitionKey("aftertask2").processInstanceId(processInstance.getId()).count()).isEqualTo(1);
+ }
+
@Test
@Deployment
public void boundaryVariableListener() {
diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml
new file mode 100644
index 00000000000..f3595d15fc9
--- /dev/null
+++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-entitylink-service/pom.xml b/modules/flowable-entitylink-service/pom.xml
index cd1f3f0ab54..da3bf184d1a 100755
--- a/modules/flowable-entitylink-service/pom.xml
+++ b/modules/flowable-entitylink-service/pom.xml
@@ -94,6 +94,9 @@
org.flowable.entitylink.service.db.mapping.entity
+
+ org.springframework*;resolution:=optional
+
diff --git a/modules/flowable-entitylink-service/src/main/java/org/flowable/entitylink/service/impl/aot/FlowableEntityLinkServiceRuntimeHints.java b/modules/flowable-entitylink-service/src/main/java/org/flowable/entitylink/service/impl/aot/FlowableEntityLinkServiceRuntimeHints.java
new file mode 100644
index 00000000000..e2554e29758
--- /dev/null
+++ b/modules/flowable-entitylink-service/src/main/java/org/flowable/entitylink/service/impl/aot/FlowableEntityLinkServiceRuntimeHints.java
@@ -0,0 +1,31 @@
+/* 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.entitylink.service.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableEntityLinkServiceRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/entitylink/service/db", resourceHints);
+
+ }
+}
diff --git a/modules/flowable-entitylink-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-entitylink-service/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..05444f89a41
--- /dev/null
+++ b/modules/flowable-entitylink-service/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.entitylink.service.impl.aot.FlowableEntityLinkServiceRuntimeHints
diff --git a/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/aot/FlowableEventRegistryRuntimeHints.java b/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/aot/FlowableEventRegistryRuntimeHints.java
new file mode 100644
index 00000000000..772beee6752
--- /dev/null
+++ b/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/aot/FlowableEventRegistryRuntimeHints.java
@@ -0,0 +1,41 @@
+/* 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.eventregistry.impl.aot;
+
+import java.util.Collections;
+
+import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar;
+import org.flowable.eventregistry.impl.db.SetChannelDefinitionTypeAndImplementationCustomChange;
+import org.flowable.eventregistry.impl.persistence.ResourceRefTypeHandler;
+import org.springframework.aot.hint.ExecutableMode;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableEventRegistryRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/eventregistry/db/mapping", hints, classLoader);
+ resourceHints.registerPattern("org/flowable/eventregistry/db/liquibase/flowable-eventregistry-db-changelog.xml");
+ hints.reflection()
+ .registerType(SetChannelDefinitionTypeAndImplementationCustomChange.class,
+ hint -> hint.withConstructor(Collections.emptyList(), ExecutableMode.INVOKE))
+ .registerType(ResourceRefTypeHandler.class, MemberCategory.values());
+ }
+}
diff --git a/modules/flowable-event-registry/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-event-registry/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..086d3dfa29e
--- /dev/null
+++ b/modules/flowable-event-registry/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.eventregistry.impl.aot.FlowableEventRegistryRuntimeHints
diff --git a/modules/flowable-eventsubscription-service/pom.xml b/modules/flowable-eventsubscription-service/pom.xml
index fca5ef8316d..7e915a2bbb1 100755
--- a/modules/flowable-eventsubscription-service/pom.xml
+++ b/modules/flowable-eventsubscription-service/pom.xml
@@ -98,6 +98,9 @@
org.flowable.eventsubscription.service.db.mapping.entity
+
+ org.springframework*;resolution:=optional
+
diff --git a/modules/flowable-eventsubscription-service/src/main/java/org/flowable/eventsubscription/service/impl/aot/FlowableEventSubscriptionServiceRuntimeHints.java b/modules/flowable-eventsubscription-service/src/main/java/org/flowable/eventsubscription/service/impl/aot/FlowableEventSubscriptionServiceRuntimeHints.java
new file mode 100644
index 00000000000..10626b2e586
--- /dev/null
+++ b/modules/flowable-eventsubscription-service/src/main/java/org/flowable/eventsubscription/service/impl/aot/FlowableEventSubscriptionServiceRuntimeHints.java
@@ -0,0 +1,30 @@
+/* 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.eventsubscription.service.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableEventSubscriptionServiceRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/eventsubscription/service/db", resourceHints);
+ }
+}
diff --git a/modules/flowable-eventsubscription-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-eventsubscription-service/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..34decbb0ccf
--- /dev/null
+++ b/modules/flowable-eventsubscription-service/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,3 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.eventsubscription.service.impl.aot.FlowableEventSubscriptionServiceRuntimeHints
+
diff --git a/modules/flowable-identitylink-service/pom.xml b/modules/flowable-identitylink-service/pom.xml
index 6eee6056147..0648c7e731c 100755
--- a/modules/flowable-identitylink-service/pom.xml
+++ b/modules/flowable-identitylink-service/pom.xml
@@ -94,6 +94,9 @@
org.flowable.identitylink.service.db.mapping.entity
+
+ org.springframework*;resolution:=optional
+
diff --git a/modules/flowable-identitylink-service/src/main/java/org/flowable/identitylink/service/impl/aot/FlowableIdentityLinkServiceRuntimeHints.java b/modules/flowable-identitylink-service/src/main/java/org/flowable/identitylink/service/impl/aot/FlowableIdentityLinkServiceRuntimeHints.java
new file mode 100644
index 00000000000..3c33e7770a4
--- /dev/null
+++ b/modules/flowable-identitylink-service/src/main/java/org/flowable/identitylink/service/impl/aot/FlowableIdentityLinkServiceRuntimeHints.java
@@ -0,0 +1,30 @@
+/* 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.identitylink.service.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableIdentityLinkServiceRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/identitylink/service/db", resourceHints);
+ }
+}
diff --git a/modules/flowable-identitylink-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-identitylink-service/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..e4a1a9f2cea
--- /dev/null
+++ b/modules/flowable-identitylink-service/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.identitylink.service.impl.aot.FlowableIdentityLinkServiceRuntimeHints
diff --git a/modules/flowable-job-service/pom.xml b/modules/flowable-job-service/pom.xml
index a4b2f496d21..0815c476c1d 100755
--- a/modules/flowable-job-service/pom.xml
+++ b/modules/flowable-job-service/pom.xml
@@ -104,9 +104,10 @@
org.flowable.job.service.db.mapping.entity
-
- jakarta.enterprise.concurrent;resolution:=optional,
-
+
+ jakarta.enterprise.concurrent;resolution:=optional,
+ org.springframework*;resolution:=optional
+
diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/aot/FlowableJobServiceRuntimeHints.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/aot/FlowableJobServiceRuntimeHints.java
new file mode 100644
index 00000000000..5205999a340
--- /dev/null
+++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/aot/FlowableJobServiceRuntimeHints.java
@@ -0,0 +1,30 @@
+/* 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.job.service.impl.aot;
+
+import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar;
+import org.springframework.aot.hint.ResourceHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class FlowableJobServiceRuntimeHints implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ ResourceHints resourceHints = hints.resources();
+ FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/job/service/db", resourceHints);
+ }
+}
diff --git a/modules/flowable-job-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-job-service/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000000..df8363ddf25
--- /dev/null
+++ b/modules/flowable-job-service/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,2 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=\
+org.flowable.job.service.impl.aot.FlowableJobServiceRuntimeHints
diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml
index f938615bcc2..55ccc930bb1 100644
--- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml
+++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml
@@ -226,7 +226,7 @@
SELECT * FROM dual
-