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 - ${limitBefore} SELECT RES.* ${limitBetween} @@ -234,7 +234,7 @@ ${limitAfter} - select count(RES.ID_) diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml index 32a1b565a42..84a5cb40765 100644 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml @@ -234,7 +234,7 @@ SELECT * FROM dual - ${limitBefore} SELECT RES.* ${limitBetween} @@ -242,7 +242,7 @@ ${limitAfter} - select count(RES.ID_) diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh new file mode 100755 index 00000000000..c4b829695e9 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +rm -rf target +mvn -DskipTests -Pnative native:compile +./target/flowable-spring-boot-sample-native diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml new file mode 100644 index 00000000000..a8ac3de9e60 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml @@ -0,0 +1,169 @@ + + 4.0.0 + + + org.flowable + flowable-spring-boot-samples + 7.1.0-SNAPSHOT + + + flowable-spring-boot-sample-native + + + + org.flowable + flowable-spring-boot-starter + + + + jakarta.persistence + jakarta.persistence-api + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.graalvm.buildtools + native-maven-plugin + + + --enable-url-protocols=https + --report-unsupported-elements-at-runtime + -H:+BuildReport + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + -agentlib:native-image-agent=config-output-dir=target/native-image + + + + + + + + + + native + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + paketobuildpacks/builder:tiny + + true + + + + + + process-aot + + process-aot + + + + + + org.graalvm.buildtools + native-maven-plugin + + ${project.build.outputDirectory} + + true + + 22.3 + + + + add-reachability-metadata + + add-reachability-metadata + + + + + + + + + + nativeTest + + + org.junit.platform + junit-platform-launcher + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + process-test-aot + + process-test-aot + + + + + + org.graalvm.buildtools + native-maven-plugin + + ${project.build.outputDirectory} + + true + + 22.3 + + + + native-test + + test + + + + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/EmailService.java b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/EmailService.java new file mode 100644 index 00000000000..d8d2c797f5c --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/EmailService.java @@ -0,0 +1,52 @@ +/* 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 com.example.flowable; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.flowable.cmmn.api.delegate.DelegatePlanItemInstance; +import org.flowable.cmmn.api.delegate.PlanItemJavaDelegate; +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Service; + +@Service +public class EmailService implements JavaDelegate, PlanItemJavaDelegate { + + protected final ConcurrentHashMap sends = new ConcurrentHashMap<>(); + + protected AtomicInteger getSendCount(String key) { + return this.sends.get(key); + } + + @Override + public void execute(DelegateExecution execution) { + internalExecute(execution); + } + + @Override + public void execute(DelegatePlanItemInstance planItemInstance) { + internalExecute(planItemInstance); + } + + protected void internalExecute(VariableContainer variableContainer) { + String customerId = (String) variableContainer.getVariable("customerId"); + String email = (String) variableContainer.getVariable("email"); + System.out.println("sending welcome email for " + customerId + " to " + email); + sends.computeIfAbsent(email, e -> new AtomicInteger()); + sends.get(email).incrementAndGet(); + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/FlowableApplication.java b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/FlowableApplication.java new file mode 100644 index 00000000000..a2202a1124c --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/FlowableApplication.java @@ -0,0 +1,116 @@ +/* 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 com.example.flowable; + +import java.util.List; +import java.util.Map; + +import org.flowable.cmmn.api.CmmnRuntimeService; +import org.flowable.cmmn.api.CmmnTaskService; +import org.flowable.cmmn.engine.CmmnEngine; +import org.flowable.dmn.engine.DmnEngine; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.task.api.Task; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.util.Assert; + +@SpringBootApplication +public class FlowableApplication { + + public static void main(String[] args) { + SpringApplication.run(FlowableApplication.class, args); + } + + @Bean + ApplicationRunner demo(ProcessEngine processEngine, CmmnEngine cmmnEngine, DmnEngine dmnEngine, EmailService emailService) { + return args -> { + startProcessInstance(processEngine, emailService); + startCaseInstance(cmmnEngine, emailService); + executeRule(dmnEngine); + }; + } + + protected void startProcessInstance(ProcessEngine processEngine, EmailService emailService) { + String customerId = "1"; + String email = "email@email.com"; + + RuntimeService runtimeService = processEngine.getRuntimeService(); + TaskService taskService = processEngine.getTaskService(); + + Map vars = Map.of("customerId", customerId, "email", email); + String processInstanceId = runtimeService.startProcessInstanceByKey("signup-process", vars).getId(); + + System.out.println("process instance ID: " + processInstanceId); + Assert.notNull(processInstanceId, "the process instance ID should not be null"); + List tasks = taskService + .createTaskQuery() + .taskName("confirm-email-task") + .includeProcessVariables() + .processVariableValueEquals("customerId", customerId) + .list(); + Assert.state(!tasks.isEmpty(), "there should be one outstanding task"); + tasks.forEach(task -> { + taskService.claim(task.getId(), "jlong"); + taskService.complete(task.getId()); + }); + Assert.isTrue(emailService.getSendCount(email).get() == 1, "there should be 1 email sent"); + } + + protected void startCaseInstance(CmmnEngine cmmnEngine, EmailService emailService) { + String customerId = "2"; + String email = "email@email.com"; + + CmmnRuntimeService cmmnRuntimeService = cmmnEngine.getCmmnRuntimeService(); + CmmnTaskService cmmnTaskService = cmmnEngine.getCmmnTaskService(); + + String caseInstanceId = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("signupCase") + .variable("customerId", customerId) + .variable("email", email) + .start() + .getId(); + + System.out.println("case instance ID: " + caseInstanceId); + Assert.notNull(caseInstanceId, "the case instance ID should not be null"); + List tasks = cmmnTaskService + .createTaskQuery() + .taskName("Confirm email task") + .includeProcessVariables() + .caseVariableValueEquals("customerId", customerId) + .list(); + Assert.state(!tasks.isEmpty(), "there should be one outstanding task"); + tasks.forEach(task -> { + cmmnTaskService.claim(task.getId(), "jbarrez"); + cmmnTaskService.complete(task.getId()); + }); + Assert.isTrue(emailService.getSendCount(email).get() == 2, "there should be 2 emails sent"); + } + + protected void executeRule(DmnEngine dmnEngine) { + Map result = dmnEngine.getDmnDecisionService().createExecuteDecisionBuilder() + .decisionKey("myDecisionTable") + .variable("customerTotalOrderPrice", 99999) + .executeWithSingleResult(); + + Assert.isTrue(result.size() == 1, "Expected one result"); + Object tier = result.get("tier"); + Assert.isTrue(tier.equals("SILVER"), "Expected SILVER as output, but was " + tier); + System.out.println("Executed DMN rule correctly"); + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties new file mode 100644 index 00000000000..4ff7f396348 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties @@ -0,0 +1,8 @@ +#spring.threads.virtual.enabled=true + +logging.level.root=INFO + +spring.sql.init.mode=always + +flowable.idm.enabled=false +flowable.jpa-enabled=false diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/cases/signupCase.cmmn b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/cases/signupCase.cmmn new file mode 100644 index 00000000000..251e51a5a41 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/cases/signupCase.cmmn @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + complete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/dmn/dmn-myDecicisionTable.dmn b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/dmn/dmn-myDecicisionTable.dmn new file mode 100644 index 00000000000..31555a837ad --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/dmn/dmn-myDecicisionTable.dmn @@ -0,0 +1,38 @@ + + + + + + customerTotalOrderPrice + + + + + + 100000]]> + + + + + + + + 10000]]> + + + + + + + + 1000]]> + + + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/processes/single-task-process.bpmn20.xml b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/processes/single-task-process.bpmn20.xml new file mode 100644 index 00000000000..1c7ead951e2 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/processes/single-task-process.bpmn20.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/test/java/com/example/flowable/FlowableApplicationTest.java b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/test/java/com/example/flowable/FlowableApplicationTest.java new file mode 100644 index 00000000000..c7139957e83 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/test/java/com/example/flowable/FlowableApplicationTest.java @@ -0,0 +1,25 @@ +/* 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 com.example.flowable; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class FlowableApplicationTest { + + @Test + void contextStarts() { + + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/pom.xml b/modules/flowable-spring-boot/flowable-spring-boot-samples/pom.xml index 81f11c3690e..307d90aa53d 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-samples/pom.xml +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/pom.xml @@ -31,6 +31,7 @@ flowable-spring-boot-sample-dmn flowable-spring-boot-sample-cmmn flowable-spring-boot-sample-ldap + flowable-spring-boot-sample-native flowable-spring-boot-sample-starter diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/BaseAutoDeployResourceContribution.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/BaseAutoDeployResourceContribution.java new file mode 100644 index 00000000000..b67b5a620d3 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/BaseAutoDeployResourceContribution.java @@ -0,0 +1,100 @@ +/* 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.aot; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Collection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aot.generate.GenerationContext; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +/** + * @author Josh Long + * @author Joram Barrez + * @author Filip Hrisafov + */ +public class BaseAutoDeployResourceContribution implements BeanFactoryInitializationAotContribution { + + protected final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + protected final Logger logger = LoggerFactory.getLogger(getClass()); + protected final String locationPrefix; + protected final Collection locationSuffixes; + + public BaseAutoDeployResourceContribution(String locationPrefix, Collection locationSuffixes) { + this.locationPrefix = locationPrefix; + this.locationSuffixes = locationSuffixes; + } + + @Override + public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + RuntimeHints runtimeHints = generationContext.getRuntimeHints(); + for (String locationSuffix : locationSuffixes) { + String path = locationPrefix + locationSuffix; + try { + for (Resource resource : resolver.getResources(path)) { + ClassPathResource classPathResource = asClasspathResource(resource); + if (classPathResource != null && classPathResource.exists()) { + logger.debug("Registering hints for {}", classPathResource); + applyToResource(classPathResource, runtimeHints); + } + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to find resources for " + path, e); + } + + } + } + + protected void applyToResource(ClassPathResource resource, RuntimeHints hints) { + hints.resources().registerResource(resource); + } + + protected ClassPathResource asClasspathResource(Resource resource) { + if (resource instanceof ClassPathResource) { + return (ClassPathResource) resource; + } + try { + logger.debug("Transforming {} to a classpath resource", resource); + String marker = "jar!"; + String externalFormOfUrl = resource.getURL().toExternalForm(); + if (externalFormOfUrl.contains(marker)) { + String rest = externalFormOfUrl.substring(externalFormOfUrl.lastIndexOf(marker) + marker.length()); + return new ClassPathResource(rest); + } else { + // ugh i think this only works for maven? what about gradle? + var classesSubstring = "classes/"; + var locationOfClassesInUrl = externalFormOfUrl.indexOf(classesSubstring); + if (locationOfClassesInUrl != -1) { + return new ClassPathResource(externalFormOfUrl.substring(locationOfClassesInUrl + classesSubstring.length())); + } + + } + + logger.error("Could not resolve {} as a classpath resource", resource); + return null; + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/cmmn/FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/cmmn/FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..10a29cd0695 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/cmmn/FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,122 @@ +/* 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.aot.cmmn; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.flowable.cmmn.converter.CmmnXmlConverter; +import org.flowable.cmmn.model.CmmnModel; +import org.flowable.cmmn.model.ImplementationType; +import org.flowable.cmmn.model.ServiceTask; +import org.flowable.spring.boot.aot.BaseAutoDeployResourceContribution; +import org.flowable.spring.boot.cmmn.FlowableCmmnProperties; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; + +/** + * @author Josh Long + * @author Joram Barrez + * @author Filip Hrisafov + */ +public class FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + protected final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + if (!ClassUtils.isPresent("org.flowable.cmmn.spring.SpringCmmnEngineConfiguration", beanFactory.getBeanClassLoader())) { + return null; + } + + if (!beanFactory.containsBean("cmmnEngineConfiguration")) { + return null; + } + + FlowableCmmnProperties properties = beanFactory.getBeanProvider(FlowableCmmnProperties.class) + .getIfAvailable(); + if (properties == null || !properties.isDeployResources()) { + return null; + } + List locationSuffixes = properties.getResourceSuffixes(); + if (locationSuffixes.isEmpty()) { + return null; + } + String locationPrefix = properties.getResourceLocation(); + if (locationPrefix.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX) || locationPrefix.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { + return new CmmnAutoDeployResourceContribution(locationPrefix, locationSuffixes, beanFactory); + } + + return null; + } + + static class CmmnAutoDeployResourceContribution extends BaseAutoDeployResourceContribution { + + protected final ConfigurableListableBeanFactory beanFactory; + + public CmmnAutoDeployResourceContribution(String locationPrefix, Collection locationSuffixes, ConfigurableListableBeanFactory beanFactory) { + super(locationPrefix, locationSuffixes); + this.beanFactory = beanFactory; + } + + @Override + protected void applyToResource(ClassPathResource resource, RuntimeHints hints) { + super.applyToResource(resource, hints); + CmmnXmlConverter cmmXmlConverter = new CmmnXmlConverter(); + CmmnModel cmmnModel = cmmXmlConverter.convertToCmmnModel(() -> { + try { + return resource.getInputStream(); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read resource " + resource, e); + } + }, false, false); + Collection serviceTasks = cmmnModel.getPrimaryCase().findPlanItemDefinitionsOfType(ServiceTask.class); + for (ServiceTask serviceTask : serviceTasks) { + if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(serviceTask.getImplementationType())) { + String expression = serviceTask.getImplementation(); + String expressionWithoutDelimiters = expression.substring(2); + expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); + String beanName = expressionWithoutDelimiters; + try { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + String beanClassName = beanDefinition.getBeanClassName(); + if (StringUtils.isNotEmpty(beanClassName)) { + hints.reflection().registerType(TypeReference.of(beanClassName), MemberCategory.values()); + logger.debug("Registering hint for bean name [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } else { + logger.debug("No bean class name for bean name [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } + + } catch (Throwable throwable) { + logger.error("Couldn't find bean named [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } + + } + } + + } + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/dmn/FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/dmn/FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..b7c4744d527 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/dmn/FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,63 @@ +/* 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.aot.dmn; + +import java.util.List; + +import org.flowable.spring.boot.aot.BaseAutoDeployResourceContribution; +import org.flowable.spring.boot.dmn.FlowableDmnProperties; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; + +/** + * @author Josh Long + * @author Joram Barrez + * @author Filip Hrisafov + */ +public class FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + protected final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + if (!ClassUtils.isPresent("org.flowable.dmn.spring.SpringDmnEngineConfiguration", beanFactory.getBeanClassLoader())) { + return null; + } + + if (!beanFactory.containsBean("dmnEngineConfiguration")) { + return null; + } + + FlowableDmnProperties properties = beanFactory.getBeanProvider(FlowableDmnProperties.class) + .getIfAvailable(); + if (properties == null || !properties.isDeployResources()) { + return null; + } + List locationSuffixes = properties.getResourceSuffixes(); + if (locationSuffixes.isEmpty()) { + return null; + } + String locationPrefix = properties.getResourceLocation(); + if (locationPrefix.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX) || locationPrefix.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { + return new BaseAutoDeployResourceContribution(locationPrefix, locationSuffixes); + } + + return null; + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/process/FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/process/FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..647368db3b5 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/process/FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,122 @@ +/* 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.aot.process; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.ImplementationType; +import org.flowable.bpmn.model.ServiceTask; +import org.flowable.spring.boot.FlowableProperties; +import org.flowable.spring.boot.aot.BaseAutoDeployResourceContribution; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; + +/** + * @author Josh Long + * @author Joram Barrez + * @author Filip Hrisafov + */ +public class FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + protected final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + if (!ClassUtils.isPresent("org.flowable.spring.SpringProcessEngineConfiguration", beanFactory.getBeanClassLoader())) { + return null; + } + + if (!beanFactory.containsBean("springProcessEngineConfiguration")) { + return null; + } + + FlowableProperties properties = beanFactory.getBeanProvider(FlowableProperties.class) + .getIfAvailable(); + if (properties == null || !properties.isCheckProcessDefinitions()) { + return null; + } + List locationSuffixes = properties.getProcessDefinitionLocationSuffixes(); + if (locationSuffixes.isEmpty()) { + return null; + } + String locationPrefix = properties.getProcessDefinitionLocationPrefix(); + if (locationPrefix.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX) || locationPrefix.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { + return new ProcessAutoDeployResourceContribution(locationPrefix, locationSuffixes, beanFactory); + } + + return null; + } + + static class ProcessAutoDeployResourceContribution extends BaseAutoDeployResourceContribution { + + protected final ConfigurableListableBeanFactory beanFactory; + + public ProcessAutoDeployResourceContribution(String locationPrefix, Collection locationSuffixes, ConfigurableListableBeanFactory beanFactory) { + super(locationPrefix, locationSuffixes); + this.beanFactory = beanFactory; + } + + @Override + protected void applyToResource(ClassPathResource resource, RuntimeHints hints) { + super.applyToResource(resource, hints); + BpmnXMLConverter xmlConverter = new BpmnXMLConverter(); + BpmnModel bpmnModel = xmlConverter.convertToBpmnModel(() -> { + try { + return resource.getInputStream(); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read resource " + resource, e); + } + }, false, false); + Collection serviceTasks = bpmnModel.getMainProcess().findFlowElementsOfType(ServiceTask.class); + for (ServiceTask serviceTask : serviceTasks) { + if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(serviceTask.getImplementationType())) { + String expression = serviceTask.getImplementation(); + String expressionWithoutDelimiters = expression.substring(2); + expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); + String beanName = expressionWithoutDelimiters; + try { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + String beanClassName = beanDefinition.getBeanClassName(); + if (StringUtils.isNotEmpty(beanClassName)) { + hints.reflection().registerType(TypeReference.of(beanClassName), MemberCategory.values()); + logger.debug("Registering hint for bean name [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } else { + logger.debug("No bean class name for bean name [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } + + } catch (Throwable throwable) { + logger.error("Couldn't find bean named [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } + + } + } + + } + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/environment/FlowableDefaultPropertiesLocationRuntimeHints.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/environment/FlowableDefaultPropertiesLocationRuntimeHints.java new file mode 100644 index 00000000000..17738ffdd48 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/environment/FlowableDefaultPropertiesLocationRuntimeHints.java @@ -0,0 +1,65 @@ +/* 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.environment; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.support.FilePatternResourceHintsRegistrar; +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * @author Filip Hrisafov + */ +public class FlowableDefaultPropertiesLocationRuntimeHints implements RuntimeHintsRegistrar { + // This is similar to what is being done in ConfigDataLocationRuntimeHints + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // This will register all the flowable-default configuration properties + FilePatternResourceHintsRegistrar.forClassPathLocations("/") + .withFileExtensions(getExtensions(classLoader)) + .withFilePrefixes("flowable-default") + .registerHints(hints.resources(), classLoader); + + } + + // The logic below is the same as for the ConfigDataLocationRuntimeHints + /** + * Get the application file extensions to consider. A valid extension starts with a + * dot. + * @param classLoader the classloader to use + * @return the configuration file extensions + */ + protected List getExtensions(ClassLoader classLoader) { + List extensions = new ArrayList<>(); + List propertySourceLoaders = getSpringFactoriesLoader(classLoader) + .load(PropertySourceLoader.class); + for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { + for (String fileExtension : propertySourceLoader.getFileExtensions()) { + String candidate = "." + fileExtension; + if (!extensions.contains(candidate)) { + extensions.add(candidate); + } + } + } + return extensions; + } + + protected SpringFactoriesLoader getSpringFactoriesLoader(ClassLoader classLoader) { + return SpringFactoriesLoader.forDefaultResourceLocation(classLoader); + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..94e8b6c6e34 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,7 @@ +org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ +org.flowable.spring.boot.aot.cmmn.FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor,\ +org.flowable.spring.boot.aot.dmn.FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor,\ +org.flowable.spring.boot.aot.process.FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor + +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.spring.boot.environment.FlowableDefaultPropertiesLocationRuntimeHints diff --git a/modules/flowable-spring-common/src/main/java/org/flowable/common/spring/CommonAutoDeploymentStrategy.java b/modules/flowable-spring-common/src/main/java/org/flowable/common/spring/CommonAutoDeploymentStrategy.java index 7c5de34b0c1..9c008133b0d 100644 --- a/modules/flowable-spring-common/src/main/java/org/flowable/common/spring/CommonAutoDeploymentStrategy.java +++ b/modules/flowable-spring-common/src/main/java/org/flowable/common/spring/CommonAutoDeploymentStrategy.java @@ -13,7 +13,6 @@ package org.flowable.common.spring; -import java.io.IOException; import java.time.Duration; import java.util.Arrays; @@ -123,7 +122,7 @@ protected String determineResourceName(final Resource resource) { } else { try { resourceName = resource.getFile().getAbsolutePath(); - } catch (IOException e) { + } catch (Exception e) { // Catching any exception here, as e.g. Graal will throw an UnsupportedException instead of an IOException resourceName = resource.getFilename(); } } diff --git a/modules/flowable-task-service/pom.xml b/modules/flowable-task-service/pom.xml index b8ababd69a6..f28940fdafe 100755 --- a/modules/flowable-task-service/pom.xml +++ b/modules/flowable-task-service/pom.xml @@ -98,6 +98,9 @@ org.flowable.task.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/aot/FlowableTaskServiceRuntimeHints.java b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/aot/FlowableTaskServiceRuntimeHints.java new file mode 100644 index 00000000000..d69afabbe49 --- /dev/null +++ b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/aot/FlowableTaskServiceRuntimeHints.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.task.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 FlowableTaskServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/task/service/db", resourceHints); + } +} diff --git a/modules/flowable-task-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-task-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..67fb77a12ab --- /dev/null +++ b/modules/flowable-task-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.task.service.impl.aot.FlowableTaskServiceRuntimeHints diff --git a/modules/flowable-variable-service/pom.xml b/modules/flowable-variable-service/pom.xml index c93aef2d5dc..de4cb3759a4 100755 --- a/modules/flowable-variable-service/pom.xml +++ b/modules/flowable-variable-service/pom.xml @@ -96,6 +96,7 @@ jakarta.persistence*;resolution:=optional, + org.springframework*;resolution:=optional diff --git a/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/aot/FlowableVariableServiceRuntimeHints.java b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/aot/FlowableVariableServiceRuntimeHints.java new file mode 100644 index 00000000000..da85a787439 --- /dev/null +++ b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/aot/FlowableVariableServiceRuntimeHints.java @@ -0,0 +1,34 @@ +/* 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.variable.service.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.flowable.variable.service.impl.db.IbatisVariableTypeHandler; +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 FlowableVariableServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/variable/service/db", resourceHints); + hints.reflection() + .registerType(IbatisVariableTypeHandler.class, MemberCategory.values()); + } +} diff --git a/modules/flowable-variable-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-variable-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..45df3f88e19 --- /dev/null +++ b/modules/flowable-variable-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.variable.service.impl.aot.FlowableVariableServiceRuntimeHints diff --git a/modules/flowable-variable-service/src/main/resources/org/flowable/variable/service/db/mapping/entity/VariableInstance.xml b/modules/flowable-variable-service/src/main/resources/org/flowable/variable/service/db/mapping/entity/VariableInstance.xml index 7d020c8cf3a..d56ce95e7c3 100644 --- a/modules/flowable-variable-service/src/main/resources/org/flowable/variable/service/db/mapping/entity/VariableInstance.xml +++ b/modules/flowable-variable-service/src/main/resources/org/flowable/variable/service/db/mapping/entity/VariableInstance.xml @@ -186,7 +186,7 @@ select * from ${prefix}ACT_RU_VARIABLE where ID_ = #{id, jdbcType=VARCHAR} - select * from ${prefix}ACT_RU_VARIABLE diff --git a/pom.xml b/pom.xml index 74657dce272..f33a3a7c41e 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 2.0.9 4.0.15 3.4.0 + 0.9.27 4.13.2 5.9.3 @@ -292,12 +293,12 @@ org.mybatis mybatis - 3.5.11 + 3.5.15 org.mybatis mybatis-spring - 3.0.0 + 3.0.3 org.mockito @@ -710,6 +711,12 @@ + + org.graalvm.buildtools + native-maven-plugin + ${native-build-tools-plugin.version} + true + org.codehaus.mojo exec-maven-plugin