From 5f1f99050b3a467e6e0b6dd7a3432b85c851b047 Mon Sep 17 00:00:00 2001 From: Tijs Rademakers Date: Tue, 15 Oct 2024 14:14:13 +0200 Subject: [PATCH] Add option to set variables async for bpmn and cmmn --- .../flowable/cmmn/api/CmmnRuntimeService.java | 8 + .../cmmn/engine/CmmnEngineConfiguration.java | 3 + .../impl/cmd/AbstractSetVariableAsyncCmd.java | 79 +++++++++ .../impl/cmd/SetLocalVariableAsyncCmd.java | 57 ++++++ .../impl/cmd/SetLocalVariablesAsyncCmd.java | 63 +++++++ .../engine/impl/cmd/SetVariableAsyncCmd.java | 57 ++++++ .../engine/impl/cmd/SetVariablesAsyncCmd.java | 63 +++++++ .../impl/job/SetAsyncVariablesJobHandler.java | 125 ++++++++++++++ .../impl/runtime/CmmnRuntimeServiceImpl.java | 24 +++ .../cmmn/test/runtime/VariablesAsyncTest.java | 162 ++++++++++++++++++ ...esAsyncTest.testSetLocalVariableAsync.cmmn | 19 ++ ...sAsyncTest.testSetLocalVariablesAsync.cmmn | 19 ++ ...riablesAsyncTest.testSetVariableAsync.cmmn | 19 ++ ...iablesAsyncTest.testSetVariablesAsync.cmmn | 19 ++ .../common/engine/api/scope/ScopeTypes.java | 7 +- .../org/flowable/engine/RuntimeService.java | 53 ++++++ .../engine/impl/RuntimeServiceImpl.java | 33 +++- .../cfg/ProcessEngineConfigurationImpl.java | 5 + .../cmd/SetAsyncExecutionVariablesCmd.java | 100 +++++++++++ .../SetAsyncVariablesJobHandler.java | 57 ++++++ .../test/api/variables/VariableAsyncTest.java | 162 ++++++++++++++++++ 21 files changed, 1131 insertions(+), 3 deletions(-) create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/AbstractSetVariableAsyncCmd.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariableAsyncCmd.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariablesAsyncCmd.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetVariableAsyncCmd.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetVariablesAsyncCmd.java create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/SetAsyncVariablesJobHandler.java create mode 100644 modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesAsyncTest.java create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetLocalVariableAsync.cmmn create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetLocalVariablesAsync.cmmn create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetVariableAsync.cmmn create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetVariablesAsync.cmmn create mode 100644 modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/SetAsyncExecutionVariablesCmd.java create mode 100644 modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/SetAsyncVariablesJobHandler.java create mode 100644 modules/flowable-engine/src/test/java/org/flowable/engine/test/api/variables/VariableAsyncTest.java diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/CmmnRuntimeService.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/CmmnRuntimeService.java index 9ff9b8909a4..c4002c03f3b 100644 --- a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/CmmnRuntimeService.java +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/CmmnRuntimeService.java @@ -216,6 +216,14 @@ public interface CmmnRuntimeService { void setLocalVariable(String planItemInstanceId, String variableName, Object variableValue); + void setVariablesAsync(String caseInstanceId, Map variables); + + void setVariableAsync(String caseInstanceId, String variableName, Object variableValue); + + void setLocalVariablesAsync(String planItemInstanceId, Map variables); + + void setLocalVariableAsync(String planItemInstanceId, String variableName, Object variableValue); + void removeVariable(String caseInstanceId, String variableName); void removeVariables(String caseInstanceId, Collection variableNames); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java index 1700f48eb62..1d8ca948434 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java @@ -105,6 +105,7 @@ import org.flowable.cmmn.engine.impl.job.CmmnHistoryCleanupJobHandler; import org.flowable.cmmn.engine.impl.job.ExternalWorkerTaskCompleteJobHandler; import org.flowable.cmmn.engine.impl.job.HistoricCaseInstanceMigrationJobHandler; +import org.flowable.cmmn.engine.impl.job.SetAsyncVariablesJobHandler; import org.flowable.cmmn.engine.impl.job.TriggerTimerEventJobHandler; import org.flowable.cmmn.engine.impl.listener.CmmnListenerFactory; import org.flowable.cmmn.engine.impl.listener.CmmnListenerNotificationHelper; @@ -1302,6 +1303,7 @@ public void initDependentScopeTypes() { this.dependentScopeTypes.add(ScopeTypes.CMMN); this.dependentScopeTypes.add(ScopeTypes.CMMN_VARIABLE_AGGREGATION); this.dependentScopeTypes.add(ScopeTypes.CMMN_EXTERNAL_WORKER); + this.dependentScopeTypes.add(ScopeTypes.CMMN_ASYNC_VARIABLES); } public void initHistoryConfigurationSettings() { @@ -1658,6 +1660,7 @@ public void initJobHandlers() { jobHandlers.put(AsyncActivatePlanItemInstanceJobHandler.TYPE, new AsyncActivatePlanItemInstanceJobHandler()); jobHandlers.put(AsyncLeaveActivePlanItemInstanceJobHandler.TYPE, new AsyncLeaveActivePlanItemInstanceJobHandler()); jobHandlers.put(AsyncInitializePlanModelJobHandler.TYPE, new AsyncInitializePlanModelJobHandler()); + jobHandlers.put(SetAsyncVariablesJobHandler.TYPE, new SetAsyncVariablesJobHandler(this)); jobHandlers.put(CmmnHistoryCleanupJobHandler.TYPE, new CmmnHistoryCleanupJobHandler()); jobHandlers.put(ExternalWorkerTaskCompleteJobHandler.TYPE, new ExternalWorkerTaskCompleteJobHandler(this)); addJobHandler(new CaseInstanceMigrationJobHandler()); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/AbstractSetVariableAsyncCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/AbstractSetVariableAsyncCmd.java new file mode 100644 index 00000000000..65780ce8b7d --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/AbstractSetVariableAsyncCmd.java @@ -0,0 +1,79 @@ +/* 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.cmd; + +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.job.SetAsyncVariablesJobHandler; +import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity; +import org.flowable.cmmn.engine.impl.persistence.entity.PlanItemInstanceEntity; +import org.flowable.common.engine.api.scope.ScopeTypes; +import org.flowable.job.service.JobService; +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.variable.service.VariableService; +import org.flowable.variable.service.impl.persistence.entity.VariableInstanceEntity; + +public abstract class AbstractSetVariableAsyncCmd { + + protected void addVariable(boolean isLocal, String scopeId, String subScopeId, String varName, Object varValue, + String tenantId, VariableService variableService) { + + VariableInstanceEntity variableInstance = variableService.createVariableInstance(varName); + variableInstance.setScopeId(scopeId); + variableInstance.setSubScopeId(subScopeId); + variableInstance.setScopeType(ScopeTypes.CMMN_ASYNC_VARIABLES); + variableInstance.setMetaInfo(String.valueOf(isLocal)); + + variableService.insertVariableInstanceWithValue(variableInstance, varValue, tenantId); + } + + protected void createSetAsyncVariablesJob(CaseInstanceEntity caseInstanceEntity, CmmnEngineConfiguration cmmnEngineConfiguration) { + JobServiceConfiguration jobServiceConfiguration = cmmnEngineConfiguration.getJobServiceConfiguration(); + JobService jobService = jobServiceConfiguration.getJobService(); + + JobEntity job = jobService.createJob(); + job.setScopeId(caseInstanceEntity.getId()); + job.setScopeDefinitionId(caseInstanceEntity.getCaseDefinitionId()); + job.setScopeType(ScopeTypes.CMMN); + job.setJobHandlerType(SetAsyncVariablesJobHandler.TYPE); + + // Inherit tenant id (if applicable) + if (caseInstanceEntity.getTenantId() != null) { + job.setTenantId(caseInstanceEntity.getTenantId()); + } + + jobService.createAsyncJob(job, true); + jobService.scheduleAsyncJob(job); + } + + protected void createSetAsyncVariablesJob(PlanItemInstanceEntity planItemInstanceEntity, CmmnEngineConfiguration cmmnEngineConfiguration) { + JobServiceConfiguration jobServiceConfiguration = cmmnEngineConfiguration.getJobServiceConfiguration(); + JobService jobService = jobServiceConfiguration.getJobService(); + + JobEntity job = jobService.createJob(); + job.setScopeId(planItemInstanceEntity.getCaseInstanceId()); + job.setSubScopeId(planItemInstanceEntity.getId()); + job.setScopeDefinitionId(planItemInstanceEntity.getCaseDefinitionId()); + job.setScopeType(ScopeTypes.CMMN); + job.setJobHandlerType(SetAsyncVariablesJobHandler.TYPE); + + // Inherit tenant id (if applicable) + if (planItemInstanceEntity.getTenantId() != null) { + job.setTenantId(planItemInstanceEntity.getTenantId()); + } + + jobService.createAsyncJob(job, true); + jobService.scheduleAsyncJob(job); + } + +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariableAsyncCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariableAsyncCmd.java new file mode 100644 index 00000000000..48966a78eea --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariableAsyncCmd.java @@ -0,0 +1,57 @@ +/* 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.cmd; + +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.persistence.entity.PlanItemInstanceEntity; +import org.flowable.cmmn.engine.impl.util.CommandContextUtil; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; + +public class SetLocalVariableAsyncCmd extends AbstractSetVariableAsyncCmd implements Command { + + protected String planItemInstanceId; + protected String variableName; + protected Object variableValue; + + public SetLocalVariableAsyncCmd(String planItemInstanceId, String variableName, Object variableValue) { + this.planItemInstanceId = planItemInstanceId; + this.variableName = variableName; + this.variableValue = variableValue; + } + + @Override + public Void execute(CommandContext commandContext) { + if (planItemInstanceId == null) { + throw new FlowableIllegalArgumentException("planItemInstanceId is null"); + } + if (variableName == null) { + throw new FlowableIllegalArgumentException("variable name is null"); + } + + CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext); + PlanItemInstanceEntity planItemInstanceEntity = cmmnEngineConfiguration.getPlanItemInstanceEntityManager().findById(planItemInstanceId); + if (planItemInstanceEntity == null) { + throw new FlowableObjectNotFoundException("No plan item instance found for id " + planItemInstanceId, PlanItemInstanceEntity.class); + } + + addVariable(true, planItemInstanceEntity.getCaseInstanceId(), planItemInstanceEntity.getId(), variableName, variableValue, planItemInstanceEntity.getTenantId(), + cmmnEngineConfiguration.getVariableServiceConfiguration().getVariableService()); + createSetAsyncVariablesJob(planItemInstanceEntity, cmmnEngineConfiguration); + + return null; + } + +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariablesAsyncCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariablesAsyncCmd.java new file mode 100644 index 00000000000..be0fdd70be6 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariablesAsyncCmd.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.cmmn.engine.impl.cmd; + +import java.util.Map; + +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.persistence.entity.PlanItemInstanceEntity; +import org.flowable.cmmn.engine.impl.util.CommandContextUtil; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; + +public class SetLocalVariablesAsyncCmd extends AbstractSetVariableAsyncCmd implements Command { + + protected String planItemInstanceId; + protected Map variables; + + public SetLocalVariablesAsyncCmd(String planItemInstanceId, Map variables) { + this.planItemInstanceId = planItemInstanceId; + this.variables = variables; + } + + @Override + public Void execute(CommandContext commandContext) { + if (planItemInstanceId == null) { + throw new FlowableIllegalArgumentException("planItemInstanceId is null"); + } + if (variables == null) { + throw new FlowableIllegalArgumentException("variables is null"); + } + if (variables.isEmpty()) { + throw new FlowableIllegalArgumentException("variables is empty"); + } + + CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext); + PlanItemInstanceEntity planItemInstanceEntity = cmmnEngineConfiguration.getPlanItemInstanceEntityManager().findById(planItemInstanceId); + if (planItemInstanceEntity == null) { + throw new FlowableObjectNotFoundException("No plan item instance found for id " + planItemInstanceId, PlanItemInstanceEntity.class); + } + + for (String variableName : variables.keySet()) { + addVariable(true, planItemInstanceEntity.getCaseInstanceId(), planItemInstanceEntity.getId(), variableName, variables.get(variableName), + planItemInstanceEntity.getTenantId(), cmmnEngineConfiguration.getVariableServiceConfiguration().getVariableService()); + } + + createSetAsyncVariablesJob(planItemInstanceEntity, cmmnEngineConfiguration); + + return null; + } + +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetVariableAsyncCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetVariableAsyncCmd.java new file mode 100644 index 00000000000..32d4d3c014e --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetVariableAsyncCmd.java @@ -0,0 +1,57 @@ +/* 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.cmd; + +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity; +import org.flowable.cmmn.engine.impl.util.CommandContextUtil; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; + +public class SetVariableAsyncCmd extends AbstractSetVariableAsyncCmd implements Command { + + protected String caseInstanceId; + protected String variableName; + protected Object variableValue; + + public SetVariableAsyncCmd(String caseInstanceId, String variableName, Object variableValue) { + this.caseInstanceId = caseInstanceId; + this.variableName = variableName; + this.variableValue = variableValue; + } + + @Override + public Void execute(CommandContext commandContext) { + if (caseInstanceId == null) { + throw new FlowableIllegalArgumentException("caseInstanceId is null"); + } + if (variableName == null) { + throw new FlowableIllegalArgumentException("variable name is null"); + } + + CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext); + CaseInstanceEntity caseInstanceEntity = cmmnEngineConfiguration.getCaseInstanceEntityManager().findById(caseInstanceId); + if (caseInstanceEntity == null) { + throw new FlowableObjectNotFoundException("No case instance found for id " + caseInstanceId, CaseInstanceEntity.class); + } + + addVariable(false, caseInstanceId, null, variableName, variableValue, caseInstanceEntity.getTenantId(), + cmmnEngineConfiguration.getVariableServiceConfiguration().getVariableService()); + createSetAsyncVariablesJob(caseInstanceEntity, cmmnEngineConfiguration); + + return null; + } + +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetVariablesAsyncCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetVariablesAsyncCmd.java new file mode 100644 index 00000000000..db66bc5f0d9 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetVariablesAsyncCmd.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.cmmn.engine.impl.cmd; + +import java.util.Map; + +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity; +import org.flowable.cmmn.engine.impl.util.CommandContextUtil; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; + +public class SetVariablesAsyncCmd extends AbstractSetVariableAsyncCmd implements Command { + + protected String caseInstanceId; + protected Map variables; + + public SetVariablesAsyncCmd(String caseInstanceId, Map variables) { + this.caseInstanceId = caseInstanceId; + this.variables = variables; + } + + @Override + public Void execute(CommandContext commandContext) { + if (caseInstanceId == null) { + throw new FlowableIllegalArgumentException("caseInstanceId is null"); + } + if (variables == null) { + throw new FlowableIllegalArgumentException("variables is null"); + } + if (variables.isEmpty()) { + throw new FlowableIllegalArgumentException("variables is empty"); + } + + CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext); + CaseInstanceEntity caseInstanceEntity = cmmnEngineConfiguration.getCaseInstanceEntityManager().findById(caseInstanceId); + if (caseInstanceEntity == null) { + throw new FlowableObjectNotFoundException("No case instance found for id " + caseInstanceId, CaseInstanceEntity.class); + } + + for (String variableName : variables.keySet()) { + addVariable(false, caseInstanceId, null, variableName, variables.get(variableName), caseInstanceEntity.getTenantId(), + cmmnEngineConfiguration.getVariableServiceConfiguration().getVariableService()); + } + + createSetAsyncVariablesJob(caseInstanceEntity, cmmnEngineConfiguration); + + return null; + } + +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/SetAsyncVariablesJobHandler.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/SetAsyncVariablesJobHandler.java new file mode 100644 index 00000000000..3eb4484c1c0 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/SetAsyncVariablesJobHandler.java @@ -0,0 +1,125 @@ +/* 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.job; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.flowable.cmmn.api.repository.CaseDefinition; +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.cmmn.engine.impl.agenda.CmmnEngineAgenda; +import org.flowable.cmmn.engine.impl.deployer.CmmnDeploymentManager; +import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity; +import org.flowable.cmmn.engine.impl.persistence.entity.CountingPlanItemInstanceEntity; +import org.flowable.cmmn.engine.impl.persistence.entity.PlanItemInstanceEntity; +import org.flowable.cmmn.engine.impl.util.CommandContextUtil; +import org.flowable.cmmn.model.Case; +import org.flowable.cmmn.model.CmmnModel; +import org.flowable.cmmn.model.VariableEventListener; +import org.flowable.common.engine.api.scope.ScopeTypes; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.job.service.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.variable.api.delegate.VariableScope; +import org.flowable.variable.service.VariableService; +import org.flowable.variable.service.impl.persistence.entity.VariableInstanceEntity; + +public class SetAsyncVariablesJobHandler implements JobHandler { + + public static final String TYPE = "cmmn-set-async-variables"; + + protected CmmnEngineConfiguration cmmnEngineConfiguration; + + public SetAsyncVariablesJobHandler(CmmnEngineConfiguration cmmnEngineConfiguration) { + this.cmmnEngineConfiguration = cmmnEngineConfiguration; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + VariableService variableService = cmmnEngineConfiguration.getVariableServiceConfiguration().getVariableService(); + + List jobVariables = null; + PlanItemInstanceEntity planItemInstanceEntity = null; + CaseInstanceEntity caseInstanceEntity = null; + String caseDefinitionId = null; + String caseInstanceId = null; + if (StringUtils.isNotEmpty(job.getSubScopeId())) { + planItemInstanceEntity = cmmnEngineConfiguration.getPlanItemInstanceEntityManager().findById(job.getSubScopeId()); + jobVariables = variableService.findVariableInstanceBySubScopeIdAndScopeType(job.getSubScopeId(), ScopeTypes.CMMN_ASYNC_VARIABLES); + caseDefinitionId = planItemInstanceEntity.getCaseDefinitionId(); + caseInstanceId = planItemInstanceEntity.getCaseInstanceId(); + + } else { + caseInstanceEntity = cmmnEngineConfiguration.getCaseInstanceEntityManager().findById(job.getScopeId()); + jobVariables = variableService.findVariableInstanceByScopeIdAndScopeType(job.getScopeId(), ScopeTypes.CMMN_ASYNC_VARIABLES); + caseDefinitionId = caseInstanceEntity.getCaseDefinitionId(); + caseInstanceId = caseInstanceEntity.getId(); + } + + if (!jobVariables.isEmpty()) { + Set variableNames = new HashSet<>(); + for (VariableInstanceEntity jobVariable : jobVariables) { + variableNames.add(jobVariable.getName()); + if (caseInstanceEntity != null) { + caseInstanceEntity.setVariable(jobVariable.getName(), jobVariable.getValue()); + } else { + planItemInstanceEntity.setVariableLocal(jobVariable.getName(), jobVariable.getValue()); + } + + variableService.deleteVariableInstance(jobVariable); + } + + if (planItemInstanceEntity != null && planItemInstanceEntity instanceof CountingPlanItemInstanceEntity) { + ((CountingPlanItemInstanceEntity) planItemInstanceEntity) + .setVariableCount(((CountingPlanItemInstanceEntity) planItemInstanceEntity).getVariableCount() - jobVariables.size()); + } + + CmmnEngineAgenda agenda = CommandContextUtil.getAgenda(commandContext); + + if (planItemInstanceEntity == null) { + CmmnDeploymentManager deploymentManager = cmmnEngineConfiguration.getDeploymentManager(); + CaseDefinition caseDefinition = deploymentManager.findDeployedCaseDefinitionById(caseDefinitionId); + boolean evaluateVariableEventListener = false; + if (caseDefinition != null) { + CmmnModel cmmnModel = deploymentManager.resolveCaseDefinition(caseDefinition).getCmmnModel(); + for (Case caze : cmmnModel.getCases()) { + List variableEventListeners = caze.findPlanItemDefinitionsOfType(VariableEventListener.class); + for (VariableEventListener variableEventListener : variableEventListeners) { + if (variableNames.contains(variableEventListener.getVariableName())) { + evaluateVariableEventListener = true; + break; + } + } + + if (evaluateVariableEventListener) { + break; + } + } + } + + if (evaluateVariableEventListener) { + agenda.planEvaluateVariableEventListenersOperation(caseInstanceId); + } + } + + agenda.planEvaluateCriteriaOperation(caseInstanceId); + } + } +} diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/CmmnRuntimeServiceImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/CmmnRuntimeServiceImpl.java index 901b8faa278..b643324d543 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/CmmnRuntimeServiceImpl.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/CmmnRuntimeServiceImpl.java @@ -79,9 +79,13 @@ import org.flowable.cmmn.engine.impl.cmd.SetCaseInstanceBusinessStatusCmd; import org.flowable.cmmn.engine.impl.cmd.SetCaseInstanceNameCmd; import org.flowable.cmmn.engine.impl.cmd.SetCaseInstanceOwnerCmd; +import org.flowable.cmmn.engine.impl.cmd.SetLocalVariableAsyncCmd; import org.flowable.cmmn.engine.impl.cmd.SetLocalVariableCmd; +import org.flowable.cmmn.engine.impl.cmd.SetLocalVariablesAsyncCmd; import org.flowable.cmmn.engine.impl.cmd.SetLocalVariablesCmd; +import org.flowable.cmmn.engine.impl.cmd.SetVariableAsyncCmd; import org.flowable.cmmn.engine.impl.cmd.SetVariableCmd; +import org.flowable.cmmn.engine.impl.cmd.SetVariablesAsyncCmd; import org.flowable.cmmn.engine.impl.cmd.SetVariablesCmd; import org.flowable.cmmn.engine.impl.cmd.StartCaseInstanceAsyncCmd; import org.flowable.cmmn.engine.impl.cmd.StartCaseInstanceCmd; @@ -287,6 +291,26 @@ public void setLocalVariable(String planItemInstanceId, String variableName, Obj public void setLocalVariables(String planItemInstanceId, Map variables) { commandExecutor.execute(new SetLocalVariablesCmd(planItemInstanceId, variables)); } + + @Override + public void setVariableAsync(String caseInstanceId, String variableName, Object variableValue) { + commandExecutor.execute(new SetVariableAsyncCmd(caseInstanceId, variableName, variableValue)); + } + + @Override + public void setVariablesAsync(String caseInstanceId, Map variables) { + commandExecutor.execute(new SetVariablesAsyncCmd(caseInstanceId, variables)); + } + + @Override + public void setLocalVariableAsync(String planItemInstanceId, String variableName, Object variableValue) { + commandExecutor.execute(new SetLocalVariableAsyncCmd(planItemInstanceId, variableName, variableValue)); + } + + @Override + public void setLocalVariablesAsync(String planItemInstanceId, Map variables) { + commandExecutor.execute(new SetLocalVariablesAsyncCmd(planItemInstanceId, variables)); + } @Override public void removeVariable(String caseInstanceId, String variableName) { diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesAsyncTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesAsyncTest.java new file mode 100644 index 00000000000..787a73f94ee --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesAsyncTest.java @@ -0,0 +1,162 @@ +/* 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.test.runtime; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; + +import org.flowable.cmmn.api.runtime.CaseInstance; +import org.flowable.cmmn.api.runtime.PlanItemInstance; +import org.flowable.cmmn.engine.impl.job.SetAsyncVariablesJobHandler; +import org.flowable.cmmn.engine.test.CmmnDeployment; +import org.flowable.cmmn.engine.test.FlowableCmmnTestCase; +import org.flowable.job.api.Job; +import org.junit.Test; + +public class VariablesAsyncTest extends FlowableCmmnTestCase { + + @Test + @CmmnDeployment + public void testSetVariableAsync() { + Map variables = new HashMap<>(); + variables.put("stringVar", "Hello World"); + variables.put("intVar", 42); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("myCase").variables(variables).start(); + + Map vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + assertThat(vars.get("stringVar")).isEqualTo("Hello World"); + assertThat(vars.get("intVar")).isEqualTo(42); + + cmmnRuntimeService.setVariableAsync(caseInstance.getId(), "extraVar", "test"); + vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + + Job asyncVarJob = cmmnManagementService.createJobQuery().caseInstanceId(caseInstance.getId()).handlerType(SetAsyncVariablesJobHandler.TYPE).singleResult(); + assertThat(asyncVarJob).isNotNull(); + + cmmnManagementService.executeJob(asyncVarJob.getId()); + + vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(3); + assertThat(vars.get("stringVar")).isEqualTo("Hello World"); + assertThat(vars.get("intVar")).isEqualTo(42); + assertThat(vars.get("extraVar")).isEqualTo("test"); + } + + @Test + @CmmnDeployment + public void testSetVariablesAsync() { + Map variables = new HashMap<>(); + variables.put("stringVar", "Hello World"); + variables.put("intVar", 42); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("myCase").variables(variables).start(); + + Map vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + assertThat(vars.get("stringVar")).isEqualTo("Hello World"); + assertThat(vars.get("intVar")).isEqualTo(42); + + Map extraVariables = new HashMap<>(); + extraVariables.put("extraVar", "test"); + extraVariables.put("extraIntVar", 77); + cmmnRuntimeService.setVariablesAsync(caseInstance.getId(), extraVariables); + vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + + Job asyncVarJob = cmmnManagementService.createJobQuery().caseInstanceId(caseInstance.getId()).handlerType(SetAsyncVariablesJobHandler.TYPE).singleResult(); + assertThat(asyncVarJob).isNotNull(); + + cmmnManagementService.executeJob(asyncVarJob.getId()); + + vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(4); + assertThat(vars.get("stringVar")).isEqualTo("Hello World"); + assertThat(vars.get("intVar")).isEqualTo(42); + assertThat(vars.get("extraVar")).isEqualTo("test"); + assertThat(vars.get("extraIntVar")).isEqualTo(77); + } + + @Test + @CmmnDeployment + public void testSetLocalVariableAsync() { + Map variables = new HashMap<>(); + variables.put("stringVar", "Hello World"); + variables.put("intVar", 42); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("myCase").variables(variables).start(); + + Map vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + assertThat(vars.get("stringVar")).isEqualTo("Hello World"); + assertThat(vars.get("intVar")).isEqualTo(42); + + PlanItemInstance planItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult(); + cmmnRuntimeService.setLocalVariableAsync(planItemInstance.getId(), "extraVar", "test"); + vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + + Job asyncVarJob = cmmnManagementService.createJobQuery().planItemInstanceId(planItemInstance.getId()).handlerType(SetAsyncVariablesJobHandler.TYPE).singleResult(); + assertThat(asyncVarJob).isNotNull(); + + cmmnManagementService.executeJob(asyncVarJob.getId()); + + vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + assertThat(vars.get("stringVar")).isEqualTo("Hello World"); + assertThat(vars.get("intVar")).isEqualTo(42); + + vars = cmmnRuntimeService.getLocalVariables(planItemInstance.getId()); + assertThat(vars).hasSize(1); + assertThat(vars.get("extraVar")).isEqualTo("test"); + } + + @Test + @CmmnDeployment + public void testSetLocalVariablesAsync() { + Map variables = new HashMap<>(); + variables.put("stringVar", "Hello World"); + variables.put("intVar", 42); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("myCase").variables(variables).start(); + + Map vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + assertThat(vars.get("stringVar")).isEqualTo("Hello World"); + assertThat(vars.get("intVar")).isEqualTo(42); + + PlanItemInstance planItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult(); + Map extraVariables = new HashMap<>(); + extraVariables.put("extraVar", "test"); + extraVariables.put("extraIntVar", 77); + cmmnRuntimeService.setLocalVariablesAsync(planItemInstance.getId(), extraVariables); + vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + + Job asyncVarJob = cmmnManagementService.createJobQuery().planItemInstanceId(planItemInstance.getId()).handlerType(SetAsyncVariablesJobHandler.TYPE).singleResult(); + assertThat(asyncVarJob).isNotNull(); + + cmmnManagementService.executeJob(asyncVarJob.getId()); + + vars = cmmnRuntimeService.getVariables(caseInstance.getId()); + assertThat(vars).hasSize(2); + assertThat(vars.get("stringVar")).isEqualTo("Hello World"); + assertThat(vars.get("intVar")).isEqualTo(42); + + vars = cmmnRuntimeService.getLocalVariables(planItemInstance.getId()); + assertThat(vars).hasSize(2); + assertThat(vars.get("extraVar")).isEqualTo("test"); + assertThat(vars.get("extraIntVar")).isEqualTo(77); + } + +} diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetLocalVariableAsync.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetLocalVariableAsync.cmmn new file mode 100644 index 00000000000..c4e573fc713 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetLocalVariableAsync.cmmn @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetLocalVariablesAsync.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetLocalVariablesAsync.cmmn new file mode 100644 index 00000000000..c4e573fc713 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetLocalVariablesAsync.cmmn @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetVariableAsync.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetVariableAsync.cmmn new file mode 100644 index 00000000000..c4e573fc713 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetVariableAsync.cmmn @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetVariablesAsync.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetVariablesAsync.cmmn new file mode 100644 index 00000000000..c4e573fc713 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesAsyncTest.testSetVariablesAsync.cmmn @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/scope/ScopeTypes.java b/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/scope/ScopeTypes.java index dc622ea8a49..1bb3889bf7a 100644 --- a/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/scope/ScopeTypes.java +++ b/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/scope/ScopeTypes.java @@ -31,6 +31,9 @@ public interface ScopeTypes { String PLAN_ITEM = "planItem"; String TASK = "task"; String EXTERNAL_WORKER = "externalWorker"; + + String CMMN_ASYNC_VARIABLES = "cmmnAsyncVariables"; + String BPMN_ASYNC_VARIABLES = "bpmnAsyncVariables"; String BPMN_VARIABLE_AGGREGATION = "bpmnVariableAggregation"; String CMMN_VARIABLE_AGGREGATION = "cmmnVariableAggregation"; @@ -38,6 +41,6 @@ public interface ScopeTypes { String CMMN_EXTERNAL_WORKER = "cmmnExternalWorker"; String BPMN_EXTERNAL_WORKER = "bpmnExternalWorker"; - Collection CMMN_DEPENDENT = new HashSet<>(Arrays.asList(CMMN, CMMN_EXTERNAL_WORKER, CMMN_VARIABLE_AGGREGATION)); - Collection BPMN_DEPENDENT = new HashSet<>(Arrays.asList(BPMN_EXTERNAL_WORKER, BPMN_VARIABLE_AGGREGATION)); + Collection CMMN_DEPENDENT = new HashSet<>(Arrays.asList(CMMN, CMMN_EXTERNAL_WORKER, CMMN_VARIABLE_AGGREGATION, CMMN_ASYNC_VARIABLES)); + Collection BPMN_DEPENDENT = new HashSet<>(Arrays.asList(BPMN_EXTERNAL_WORKER, BPMN_VARIABLE_AGGREGATION, BPMN_ASYNC_VARIABLES)); } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/RuntimeService.java b/modules/flowable-engine/src/main/java/org/flowable/engine/RuntimeService.java index 0c9f65de0ea..3f61268b8ae 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/RuntimeService.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/RuntimeService.java @@ -864,6 +864,59 @@ public interface RuntimeService { * when no execution is found for the given executionId. */ void setVariablesLocal(String executionId, Map variables); + + /** + * Update or create a variable for an execution asynchronously. + * + * @param executionId + * id of execution to set variable in, cannot be null. + * @param variableName + * name of variable to set, cannot be null. + * @param value + * value to set. When null is passed, the variable is not removed, only it's value will be set to null. + * @throws FlowableObjectNotFoundException + * when no execution is found for the given executionId. + */ + void setVariableAsync(String executionId, String variableName, Object value); + + /** + * Update or create a variable for an execution (not considering parent scopes) asynchronously. If the variable is not already existing, it will be created in the given execution. + * + * @param executionId + * id of execution to set variable in, cannot be null. + * @param variableName + * name of variable to set, cannot be null. + * @param value + * value to set. When null is passed, the variable is not removed, only it's value will be set to null. + * @throws FlowableObjectNotFoundException + * when no execution is found for the given executionId. + */ + void setVariableLocalAsync(String executionId, String variableName, Object value); + + /** + * Update or create given variables for an execution (including parent scopes) asynchronously. + * + * @param executionId + * id of the execution, cannot be null. + * @param variables + * map containing name (key) and value of variables, can be null. + * @throws FlowableObjectNotFoundException + * when no execution is found for the given executionId. + * @see VariableScope#setVariables(Map) {@link VariableScope#setVariables(Map)} + */ + void setVariablesAsync(String executionId, Map variables); + + /** + * Update or create given variables for an execution (not considering parent scopes) asynchronously. If the variables are not already existing, it will be created in the given execution. + * + * @param executionId + * id of the execution, cannot be null. + * @param variables + * map containing name (key) and value of variables, can be null. + * @throws FlowableObjectNotFoundException + * when no execution is found for the given executionId. + */ + void setVariablesLocalAsync(String executionId, Map variables); /** * Removes a variable for an execution. diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/RuntimeServiceImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/RuntimeServiceImpl.java index f274cd2c94b..a3b48ae4ffd 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/RuntimeServiceImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/RuntimeServiceImpl.java @@ -39,8 +39,8 @@ import org.flowable.engine.impl.cmd.DeleteIdentityLinkForProcessInstanceCmd; import org.flowable.engine.impl.cmd.DeleteMultiInstanceExecutionCmd; import org.flowable.engine.impl.cmd.DeleteProcessInstanceCmd; -import org.flowable.engine.impl.cmd.DeleteProcessInstancesByIdCmd; import org.flowable.engine.impl.cmd.DeleteProcessInstanceStartEventSubscriptionCmd; +import org.flowable.engine.impl.cmd.DeleteProcessInstancesByIdCmd; import org.flowable.engine.impl.cmd.DispatchEventCommand; import org.flowable.engine.impl.cmd.EvaluateConditionalEventsCmd; import org.flowable.engine.impl.cmd.ExecuteActivityForAdhocSubProcessCmd; @@ -72,6 +72,7 @@ import org.flowable.engine.impl.cmd.RemoveExecutionVariablesCmd; import org.flowable.engine.impl.cmd.RemoveProcessInstanceAssigneeCmd; import org.flowable.engine.impl.cmd.RemoveProcessInstanceOwnerCmd; +import org.flowable.engine.impl.cmd.SetAsyncExecutionVariablesCmd; import org.flowable.engine.impl.cmd.SetExecutionVariablesCmd; import org.flowable.engine.impl.cmd.SetProcessInstanceAssigneeCmd; import org.flowable.engine.impl.cmd.SetProcessInstanceBusinessKeyCmd; @@ -360,6 +361,36 @@ public void setVariables(String executionId, Map variables) { public void setVariablesLocal(String executionId, Map variables) { commandExecutor.execute(new SetExecutionVariablesCmd(executionId, variables, true)); } + + @Override + public void setVariableAsync(String executionId, String variableName, Object value) { + if (variableName == null) { + throw new FlowableIllegalArgumentException("variableName is null"); + } + Map variables = new HashMap<>(); + variables.put(variableName, value); + commandExecutor.execute(new SetAsyncExecutionVariablesCmd(executionId, variables, false)); + } + + @Override + public void setVariableLocalAsync(String executionId, String variableName, Object value) { + if (variableName == null) { + throw new FlowableIllegalArgumentException("variableName is null"); + } + Map variables = new HashMap<>(); + variables.put(variableName, value); + commandExecutor.execute(new SetAsyncExecutionVariablesCmd(executionId, variables, true)); + } + + @Override + public void setVariablesAsync(String executionId, Map variables) { + commandExecutor.execute(new SetAsyncExecutionVariablesCmd(executionId, variables, false)); + } + + @Override + public void setVariablesLocalAsync(String executionId, Map variables) { + commandExecutor.execute(new SetAsyncExecutionVariablesCmd(executionId, variables, true)); + } @Override public void removeVariable(String executionId, String variableName) { diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java index adbf7cbb41b..7d529ec7ed3 100755 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java @@ -275,6 +275,7 @@ import org.flowable.engine.impl.jobexecutor.ProcessEventJobHandler; import org.flowable.engine.impl.jobexecutor.ProcessInstanceMigrationJobHandler; import org.flowable.engine.impl.jobexecutor.ProcessInstanceMigrationStatusJobHandler; +import org.flowable.engine.impl.jobexecutor.SetAsyncVariablesJobHandler; import org.flowable.engine.impl.jobexecutor.TimerActivateProcessDefinitionHandler; import org.flowable.engine.impl.jobexecutor.TimerStartEventJobHandler; import org.flowable.engine.impl.jobexecutor.TimerSuspendProcessDefinitionHandler; @@ -1286,6 +1287,7 @@ public void initVariableAggregator() { public void initDependentScopeTypes() { this.dependentScopeTypes.add(ScopeTypes.BPMN_VARIABLE_AGGREGATION); this.dependentScopeTypes.add(ScopeTypes.BPMN_EXTERNAL_WORKER); + this.dependentScopeTypes.add(ScopeTypes.BPMN_ASYNC_VARIABLES); } // History manager /////////////////////////////////////////////////////////// @@ -2017,6 +2019,9 @@ public void initJobHandlers() { ProcessInstanceMigrationStatusJobHandler processInstanceMigrationStatusJobHandler = new ProcessInstanceMigrationStatusJobHandler(); jobHandlers.put(processInstanceMigrationStatusJobHandler.getType(), processInstanceMigrationStatusJobHandler); + + SetAsyncVariablesJobHandler setAsyncVariablesJobHandler = new SetAsyncVariablesJobHandler(); + jobHandlers.put(setAsyncVariablesJobHandler.getType(), setAsyncVariablesJobHandler); ExternalWorkerTaskCompleteJobHandler externalWorkerTaskCompleteJobHandler = new ExternalWorkerTaskCompleteJobHandler(); jobHandlers.put(externalWorkerTaskCompleteJobHandler.getType(), externalWorkerTaskCompleteJobHandler); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/SetAsyncExecutionVariablesCmd.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/SetAsyncExecutionVariablesCmd.java new file mode 100644 index 00000000000..42d1902e890 --- /dev/null +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/SetAsyncExecutionVariablesCmd.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.engine.impl.cmd; + +import java.util.Map; + +import org.flowable.common.engine.api.scope.ScopeTypes; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.jobexecutor.SetAsyncVariablesJobHandler; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.CountingEntityUtil; +import org.flowable.job.service.JobService; +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.variable.service.VariableService; +import org.flowable.variable.service.VariableServiceConfiguration; +import org.flowable.variable.service.impl.persistence.entity.VariableInstanceEntity; + +public class SetAsyncExecutionVariablesCmd extends NeedsActiveExecutionCmd { + + private static final long serialVersionUID = 1L; + + protected Map variables; + protected boolean isLocal; + + public SetAsyncExecutionVariablesCmd(String executionId, Map variables, boolean isLocal) { + super(executionId); + this.variables = variables; + this.isLocal = isLocal; + } + + @Override + protected Object execute(CommandContext commandContext, ExecutionEntity execution) { + if (variables != null && !variables.isEmpty()) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + VariableServiceConfiguration variableServiceConfiguration = processEngineConfiguration.getVariableServiceConfiguration(); + VariableService variableService = variableServiceConfiguration.getVariableService(); + + for (String variableName : variables.keySet()) { + addVariable(isLocal, execution.getProcessInstanceId(), execution.getId(), variableName, variables.get(variableName), + execution.getTenantId(), variableService); + } + + createSetAsyncVariablesJob(execution, processEngineConfiguration); + } + + return null; + } + + protected void addVariable(boolean isLocal, String scopeId, String subScopeId, String varName, Object varValue, + String tenantId, VariableService variableService) { + + VariableInstanceEntity variableInstance = variableService.createVariableInstance(varName); + variableInstance.setScopeId(scopeId); + variableInstance.setSubScopeId(subScopeId); + variableInstance.setScopeType(ScopeTypes.BPMN_ASYNC_VARIABLES); + variableInstance.setMetaInfo(String.valueOf(isLocal)); + + variableService.insertVariableInstanceWithValue(variableInstance, varValue, tenantId); + + CountingEntityUtil.handleInsertVariableInstanceEntityCount(variableInstance); + } + + protected void createSetAsyncVariablesJob(ExecutionEntity execution, ProcessEngineConfigurationImpl processEngineConfiguration) { + JobServiceConfiguration jobServiceConfiguration = processEngineConfiguration.getJobServiceConfiguration(); + JobService jobService = jobServiceConfiguration.getJobService(); + + JobEntity job = jobService.createJob(); + job.setExecutionId(execution.getId()); + job.setProcessInstanceId(execution.getProcessInstanceId()); + job.setProcessDefinitionId(execution.getProcessDefinitionId()); + job.setJobHandlerType(SetAsyncVariablesJobHandler.TYPE); + + // Inherit tenant id (if applicable) + if (execution.getTenantId() != null) { + job.setTenantId(execution.getTenantId()); + } + + jobService.createAsyncJob(job, true); + jobService.scheduleAsyncJob(job); + } + + @Override + protected String getSuspendedExceptionMessagePrefix() { + return "Cannot set variables to"; + } + +} diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/SetAsyncVariablesJobHandler.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/SetAsyncVariablesJobHandler.java new file mode 100644 index 00000000000..412658c7388 --- /dev/null +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/SetAsyncVariablesJobHandler.java @@ -0,0 +1,57 @@ +/* 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.jobexecutor; + +import java.util.List; + +import org.flowable.common.engine.api.scope.ScopeTypes; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.CountingEntityUtil; +import org.flowable.job.service.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.variable.api.delegate.VariableScope; +import org.flowable.variable.service.VariableService; +import org.flowable.variable.service.impl.persistence.entity.VariableInstanceEntity; + +public class SetAsyncVariablesJobHandler implements JobHandler { + + public static final String TYPE = "set-async-variables"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + ExecutionEntity executionEntity = (ExecutionEntity) variableScope; + + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + VariableService variableService = processEngineConfiguration.getVariableServiceConfiguration().getVariableService(); + List jobVariables = variableService.findVariableInstanceBySubScopeIdAndScopeType(executionEntity.getId(), ScopeTypes.BPMN_ASYNC_VARIABLES); + for (VariableInstanceEntity jobVariable : jobVariables) { + if ("true".equalsIgnoreCase(jobVariable.getMetaInfo())) { + executionEntity.setVariableLocal(jobVariable.getName(), jobVariable.getValue()); + } else { + executionEntity.setVariable(jobVariable.getName(), jobVariable.getValue()); + } + + CountingEntityUtil.handleDeleteVariableInstanceEntityCount(jobVariable, false); + variableService.deleteVariableInstance(jobVariable); + } + } + +} diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/variables/VariableAsyncTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/variables/VariableAsyncTest.java new file mode 100644 index 00000000000..b7c12a7c160 --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/variables/VariableAsyncTest.java @@ -0,0 +1,162 @@ +/* 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.test.api.variables; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; + +import org.flowable.engine.impl.jobexecutor.SetAsyncVariablesJobHandler; +import org.flowable.engine.impl.test.PluggableFlowableTestCase; +import org.flowable.engine.runtime.Execution; +import org.flowable.engine.test.Deployment; +import org.flowable.job.api.Job; +import org.junit.jupiter.api.Test; + +public class VariableAsyncTest extends PluggableFlowableTestCase { + + @Test + @Deployment(resources = "org/flowable/engine/test/api/variables/VariablesTest.bpmn20.xml") + public void testSetVariableAsync() { + Map vars = new HashMap<>(); + vars.put("name", "John Doe"); + vars.put("amount", 99); + String processInstanceId = runtimeService.startProcessInstanceByKey("variablesTest", vars).getId(); + + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + assertThat(vars.get("name")).isEqualTo("John Doe"); + assertThat(vars.get("amount")).isEqualTo(99); + + runtimeService.setVariableAsync(processInstanceId, "asyncVar", "test"); + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + + Job asyncVarJob = managementService.createJobQuery().processInstanceId(processInstanceId).handlerType(SetAsyncVariablesJobHandler.TYPE).singleResult(); + assertThat(asyncVarJob).isNotNull(); + + managementService.executeJob(asyncVarJob.getId()); + + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(3); + assertThat(vars.get("name")).isEqualTo("John Doe"); + assertThat(vars.get("amount")).isEqualTo(99); + assertThat(vars.get("asyncVar")).isEqualTo("test"); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/api/variables/VariablesTest.bpmn20.xml") + public void testSetVariablesAsync() { + Map vars = new HashMap<>(); + vars.put("name", "John Doe"); + vars.put("amount", 99); + String processInstanceId = runtimeService.startProcessInstanceByKey("variablesTest", vars).getId(); + + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + assertThat(vars.get("name")).isEqualTo("John Doe"); + assertThat(vars.get("amount")).isEqualTo(99); + + Map asyncVars = new HashMap<>(); + asyncVars.put("asyncVar", "test"); + asyncVars.put("anotherVar", 33); + runtimeService.setVariablesAsync(processInstanceId, asyncVars); + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + + Job asyncVarJob = managementService.createJobQuery().processInstanceId(processInstanceId).handlerType(SetAsyncVariablesJobHandler.TYPE).singleResult(); + assertThat(asyncVarJob).isNotNull(); + + managementService.executeJob(asyncVarJob.getId()); + + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(4); + assertThat(vars.get("name")).isEqualTo("John Doe"); + assertThat(vars.get("amount")).isEqualTo(99); + assertThat(vars.get("asyncVar")).isEqualTo("test"); + assertThat(vars.get("anotherVar")).isEqualTo(33); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/api/variables/VariablesTest.bpmn20.xml") + public void testSetVariableLocalAsync() { + Map vars = new HashMap<>(); + vars.put("name", "John Doe"); + vars.put("amount", 99); + String processInstanceId = runtimeService.startProcessInstanceByKey("variablesTest", vars).getId(); + + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + assertThat(vars.get("name")).isEqualTo("John Doe"); + assertThat(vars.get("amount")).isEqualTo(99); + + Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).onlyChildExecutions().singleResult(); + + runtimeService.setVariableLocalAsync(execution.getId(), "asyncVar", "test"); + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + vars = runtimeService.getVariablesLocal(execution.getId()); + assertThat(vars).hasSize(0); + + Job asyncVarJob = managementService.createJobQuery().processInstanceId(processInstanceId).handlerType(SetAsyncVariablesJobHandler.TYPE).singleResult(); + assertThat(asyncVarJob).isNotNull(); + + managementService.executeJob(asyncVarJob.getId()); + + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + + vars = runtimeService.getVariablesLocal(execution.getId()); + assertThat(vars).hasSize(1); + assertThat(vars.get("asyncVar")).isEqualTo("test"); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/api/variables/VariablesTest.bpmn20.xml") + public void testSetVariablesLocalAsync() { + Map vars = new HashMap<>(); + vars.put("name", "John Doe"); + vars.put("amount", 99); + String processInstanceId = runtimeService.startProcessInstanceByKey("variablesTest", vars).getId(); + + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + assertThat(vars.get("name")).isEqualTo("John Doe"); + assertThat(vars.get("amount")).isEqualTo(99); + + Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).onlyChildExecutions().singleResult(); + + Map asyncVars = new HashMap<>(); + asyncVars.put("asyncVar", "test"); + asyncVars.put("anotherVar", 33); + runtimeService.setVariablesLocalAsync(execution.getId(), asyncVars); + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + + Job asyncVarJob = managementService.createJobQuery().processInstanceId(processInstanceId).handlerType(SetAsyncVariablesJobHandler.TYPE).singleResult(); + assertThat(asyncVarJob).isNotNull(); + + managementService.executeJob(asyncVarJob.getId()); + + vars = runtimeService.getVariables(processInstanceId); + assertThat(vars).hasSize(2); + assertThat(vars.get("name")).isEqualTo("John Doe"); + assertThat(vars.get("amount")).isEqualTo(99); + + vars = runtimeService.getVariablesLocal(execution.getId()); + assertThat(vars).hasSize(2); + assertThat(vars.get("asyncVar")).isEqualTo("test"); + assertThat(vars.get("anotherVar")).isEqualTo(33); + } +}