From 00c47a1bafeb3d459a57ecef7718050e3066bc16 Mon Sep 17 00:00:00 2001 From: Christopher Welsch Date: Mon, 16 Feb 2026 17:43:17 +0100 Subject: [PATCH 1/3] added definition variable container to support EL Resolvers to use the definition if no VariableScope is available (during deployment) --- .../DefinitionVariableContainer.java | 79 ++++++++ .../deployer/EventSubscriptionManager.java | 12 +- .../impl/bpmn/deployer/TimerManager.java | 19 +- .../dynamic/AbstractDynamicStateManager.java | 9 +- .../event/EventDefinitionExpressionUtil.java | 51 +++++ ...tProcessDefinitionDeletionManagerImpl.java | 17 +- .../impl/util/ProcessInstanceHelper.java | 10 +- .../flowable/engine/impl/util/TimerUtil.java | 165 +++++++++------- ...ExpressionWithDefinitionContainerTest.java | 177 ++++++++++++++++++ .../el/DefinitionVariableContainerTest.java | 64 +++++++ ...tExpressionResolvesAtDeployment.bpmn20.xml | 40 ++++ ...tExpressionResolvesAtDeployment.bpmn20.xml | 40 ++++ ...tExpressionResolvesAtDeployment.bpmn20.xml | 41 ++++ 13 files changed, 635 insertions(+), 89 deletions(-) create mode 100644 modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java create mode 100644 modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java create mode 100644 modules/flowable-engine/src/test/java/org/flowable/engine/test/el/DefinitionVariableContainerTest.java create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml diff --git a/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java b/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java new file mode 100644 index 00000000000..c27781d44e4 --- /dev/null +++ b/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java @@ -0,0 +1,79 @@ +/* + * Copyright 2026, Flowable Licences AG. + * This license is based on the software license agreement and terms and conditions in effect between the parties + * at the time of purchase of the Flowable software product. + * Your agreement to these terms and conditions is required to install or use the Flowable software product and/or this file. + * Flowable is a trademark of Flowable AG registered in several countries. + */ +package org.flowable.common.engine.api.definition; + +import java.util.Set; + +import org.flowable.common.engine.api.variable.VariableContainer; + +/** + * A {@link VariableContainer} that carries definition context (definitionId, deploymentId, scopeType, tenantId) + * for expression evaluation when no execution is active (e.g. during deployment). + * This allows EL resolvers to look up the parent deployment and resolve definition-scoped expressions. + * + * @author Christopher Welsch + */ +public class DefinitionVariableContainer implements VariableContainer { + + protected String definitionId; + protected String deploymentId; + protected String scopeType; + protected String tenantId; + + public DefinitionVariableContainer() { + } + + public DefinitionVariableContainer(String definitionId, String deploymentId, String scopeType, String tenantId) { + this.definitionId = definitionId; + this.deploymentId = deploymentId; + this.scopeType = scopeType; + this.tenantId = tenantId; + } + + public String getDefinitionId() { + return definitionId; + } + + public String getDeploymentId() { + return deploymentId; + } + + public String getScopeType() { + return scopeType; + } + + @Override + public String getTenantId() { + return tenantId; + } + + @Override + public boolean hasVariable(String variableName) { + return false; + } + + @Override + public Object getVariable(String variableName) { + return null; + } + + @Override + public void setVariable(String variableName, Object variableValue) { + + } + + @Override + public void setTransientVariable(String variableName, Object variableValue) { + + } + + @Override + public Set getVariableNames() { + return Set.of(); + } +} diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/EventSubscriptionManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/EventSubscriptionManager.java index f3abf83f729..e2ea5f0c7bb 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/EventSubscriptionManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/EventSubscriptionManager.java @@ -45,6 +45,7 @@ import org.flowable.eventsubscription.service.impl.persistence.entity.EventSubscriptionEntity; import org.flowable.eventsubscription.service.impl.persistence.entity.MessageEventSubscriptionEntity; import org.flowable.eventsubscription.service.impl.persistence.entity.SignalEventSubscriptionEntity; +import org.flowable.common.engine.api.definition.DefinitionVariableContainer; /** * Manages event subscriptions for newly-deployed process definitions and their previous versions. @@ -173,7 +174,10 @@ protected void insertSignalEvent(SignalEventDefinition signalEventDefinition, St EventSubscriptionService eventSubscriptionService = processEngineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService(); SignalEventSubscriptionEntity subscriptionEntity = eventSubscriptionService.createSignalEventSubscription(); - String signalName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel,null); + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), processDefinition.getDeploymentId(), + ScopeTypes.BPMN, processDefinition.getTenantId()); + + String signalName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, definitionVariableContainer); subscriptionEntity.setEventName(signalName); subscriptionEntity.setActivityId(startEvent.getId()); @@ -191,8 +195,12 @@ protected void insertMessageEvent(MessageEventDefinition messageEventDefinition, ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); EventSubscriptionService eventSubscriptionService = processEngineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService(); + + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), processDefinition.getDeploymentId(), + ScopeTypes.BPMN, processDefinition.getTenantId()); + // look for subscriptions for the same name in db: - String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, null); + String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); List subscriptionsForSameMessageName = eventSubscriptionService .findEventSubscriptionsByName(MessageEventHandler.EVENT_HANDLER_TYPE, messageName, processDefinition.getTenantId()); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/TimerManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/TimerManager.java index e3966f87a64..aeee036f5d4 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/TimerManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/TimerManager.java @@ -20,6 +20,7 @@ import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.StartEvent; import org.flowable.bpmn.model.TimerEventDefinition; +import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.context.Context; import org.flowable.common.engine.impl.util.CollectionUtil; import org.flowable.engine.ProcessEngineConfiguration; @@ -32,6 +33,7 @@ import org.flowable.job.service.TimerJobService; import org.flowable.job.service.impl.cmd.CancelJobsCmd; import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; +import org.flowable.common.engine.api.definition.DefinitionVariableContainer; /** * Manages timers for newly-deployed process definitions and their previous versions. @@ -73,18 +75,21 @@ protected List getTimerDeclarations(ProcessDefinitionEntity proc if (CollectionUtil.isNotEmpty(startEvent.getEventDefinitions())) { EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0); if (eventDefinition instanceof TimerEventDefinition timerEventDefinition) { + + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), + processDefinition.getDeploymentId(), + ScopeTypes.BPMN, processDefinition.getTenantId()); + TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, startEvent, - false, null, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), + false, definitionVariableContainer, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); - if (timerJob != null) { - timerJob.setProcessDefinitionId(processDefinition.getId()); + timerJob.setProcessDefinitionId(processDefinition.getId()); - if (processDefinition.getTenantId() != null) { - timerJob.setTenantId(processDefinition.getTenantId()); - } - timers.add(timerJob); + if (processDefinition.getTenantId() != null) { + timerJob.setTenantId(processDefinition.getTenantId()); } + timers.add(timerJob); } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java index c5d1e698665..d6a2b77dbd7 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java @@ -55,6 +55,7 @@ import org.flowable.bpmn.model.UserTask; import org.flowable.bpmn.model.ValuedDataObject; import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.definition.DefinitionVariableContainer; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; @@ -1585,7 +1586,9 @@ protected void processCreatedEventSubProcess(EventSubProcess eventSubProcess, Ex messageExecution.setEventScope(true); messageExecution.setActive(false); - String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, null); + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(messageExecution.getProcessDefinitionId(), + eventSubProcessExecution.getDeploymentId(), ScopeTypes.BPMN, messageExecution.getTenantId()); + String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); EventSubscriptionEntity messageSubscription = (EventSubscriptionEntity) eventSubscriptionService.createEventSubscriptionBuilder() .eventType(MessageEventSubscriptionEntity.EVENT_TYPE) .eventName(messageName) @@ -1618,7 +1621,9 @@ protected void processCreatedEventSubProcess(EventSubProcess eventSubProcess, Ex signalExecution.setEventScope(true); signalExecution.setActive(false); - String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, null); + DefinitionVariableContainer signalDefinitionVariableContainer = new DefinitionVariableContainer(signalExecution.getProcessDefinitionId(), + eventSubProcessExecution.getDeploymentId(), ScopeTypes.BPMN, signalExecution.getTenantId()); + String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, signalDefinitionVariableContainer); EventSubscriptionEntity signalSubscription = (EventSubscriptionEntity) eventSubscriptionService.createEventSubscriptionBuilder() .eventType(SignalEventSubscriptionEntity.EVENT_TYPE) diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/EventDefinitionExpressionUtil.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/EventDefinitionExpressionUtil.java index 95ab16e636d..50798299075 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/EventDefinitionExpressionUtil.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/EventDefinitionExpressionUtil.java @@ -21,6 +21,7 @@ import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.variable.service.impl.el.NoExecutionVariableScope; /** @@ -57,6 +58,34 @@ public static String determineSignalName(CommandContext commandContext, SignalEv return signalName; } + /** + * Determines signal name from definition or expression + */ + public static String determineSignalName(CommandContext commandContext, SignalEventDefinition signalEventDefinition, BpmnModel bpmnModel, + VariableContainer definitionVariableContainer) { + String signalName = null; + + if (StringUtils.isNotEmpty(signalEventDefinition.getSignalRef())) { + Signal signal = bpmnModel.getSignal(signalEventDefinition.getSignalRef()); + if (signal != null) { + signalName = signal.getName(); + } else { + signalName = signalEventDefinition.getSignalRef(); + } + + } else { + signalName = signalEventDefinition.getSignalExpression(); + + } + + if (StringUtils.isNotEmpty(signalName)) { + Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(signalName); + return expression.getValue(definitionVariableContainer).toString(); + } + + return signalName; + } + /** * Determines the event name of the {@link org.flowable.bpmn.model.MessageEventDefinition} that is passed: * - if a message ref is set, it has precedence @@ -83,4 +112,26 @@ public static String determineMessageName(CommandContext commandContext, Message return messageName; } + /** + * Determines message name from definition or expression + */ + public static String determineMessageName(CommandContext commandContext, MessageEventDefinition messageEventDefinition, + VariableContainer definitionVariableContainer) { + String messageName = null; + + if (StringUtils.isNotEmpty(messageEventDefinition.getMessageRef())) { + return messageEventDefinition.getMessageRef(); + + } else { + messageName = messageEventDefinition.getMessageExpression(); + + } + + if (StringUtils.isNotEmpty(messageName)) { + Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(messageName); + return expression.getValue(definitionVariableContainer).toString(); + } + + return messageName; + } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/repository/DeploymentProcessDefinitionDeletionManagerImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/repository/DeploymentProcessDefinitionDeletionManagerImpl.java index b87b9393dad..a6349709551 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/repository/DeploymentProcessDefinitionDeletionManagerImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/repository/DeploymentProcessDefinitionDeletionManagerImpl.java @@ -50,6 +50,7 @@ import org.flowable.eventsubscription.service.impl.persistence.entity.SignalEventSubscriptionEntity; import org.flowable.job.service.TimerJobService; import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; +import org.flowable.common.engine.api.definition.DefinitionVariableContainer; /** * @author Filip Hrisafov @@ -154,13 +155,17 @@ protected void restorePreviousStartEventsIfNeeded(ProcessDefinition processDefin protected void restoreTimerStartEvent(ProcessDefinition previousProcessDefinition, StartEvent startEvent, EventDefinition eventDefinition) { TimerEventDefinition timerEventDefinition = (TimerEventDefinition) eventDefinition; + + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(previousProcessDefinition.getId(), + previousProcessDefinition.getDeploymentId(), ScopeTypes.BPMN, previousProcessDefinition.getTenantId()); + TimerJobEntity timer = TimerUtil.createTimerEntityForTimerEventDefinition((TimerEventDefinition) eventDefinition, startEvent, - false, null, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), + false, definitionVariableContainer, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); if (timer != null) { TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, startEvent, - false, null, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), + false, definitionVariableContainer, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); timerJob.setProcessDefinitionId(previousProcessDefinition.getId()); @@ -178,7 +183,9 @@ protected void restoreSignalStartEvent(ProcessDefinition previousProcessDefiniti SignalEventDefinition signalEventDefinition = (SignalEventDefinition) eventDefinition; SignalEventSubscriptionEntity subscriptionEntity = engineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService().createSignalEventSubscription(); - String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, null); + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(previousProcessDefinition.getId(), + previousProcessDefinition.getDeploymentId(), ScopeTypes.BPMN, previousProcessDefinition.getTenantId()); + String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, definitionVariableContainer); subscriptionEntity.setEventName(eventName); subscriptionEntity.setActivityId(startEvent.getId()); subscriptionEntity.setProcessDefinitionId(previousProcessDefinition.getId()); @@ -199,7 +206,9 @@ protected void restoreMessageStartEvent(ProcessDefinition previousProcessDefinit CommandContext commandContext = Context.getCommandContext(); MessageEventSubscriptionEntity newSubscription = engineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService().createMessageEventSubscription(); - String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, null); + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(previousProcessDefinition.getId(), + previousProcessDefinition.getDeploymentId(), ScopeTypes.BPMN, previousProcessDefinition.getTenantId()); + String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); newSubscription.setEventName(messageName); newSubscription.setActivityId(startEvent.getId()); newSubscription.setConfiguration(previousProcessDefinition.getId()); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessInstanceHelper.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessInstanceHelper.java index 158dc1c592c..5c5c1bde947 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessInstanceHelper.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessInstanceHelper.java @@ -64,6 +64,7 @@ import org.flowable.eventsubscription.service.impl.persistence.entity.SignalEventSubscriptionEntity; import org.flowable.identitylink.api.IdentityLinkType; import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; +import org.flowable.common.engine.api.definition.DefinitionVariableContainer; import tools.jackson.databind.node.ObjectNode; @@ -157,7 +158,10 @@ public ProcessInstance createAndStartProcessInstanceByMessage(ProcessDefinition if (CollectionUtil.isNotEmpty(startEvent.getEventDefinitions()) && startEvent.getEventDefinitions() .get(0) instanceof MessageEventDefinition messageEventDefinition) { - String actualMessageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, null); + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), processDefinition.getDeploymentId(), + ScopeTypes.BPMN, processDefinition.getTenantId()); + + String actualMessageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); if (Objects.equals(actualMessageName, messageName)) { initialFlowElement = flowElement; break; @@ -461,7 +465,9 @@ protected void handleSignalEventSubscription(EventDefinition eventDefinition, St signalExecution.setEventScope(true); signalExecution.setActive(false); - String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, null); + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(parentExecution.getProcessDefinitionId(), + parentExecution.getDeploymentId(), ScopeTypes.BPMN, parentExecution.getTenantId()); + String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, definitionVariableContainer); EventSubscriptionEntity eventSubscription = (EventSubscriptionEntity) processEngineConfiguration.getEventSubscriptionServiceConfiguration() .getEventSubscriptionService().createEventSubscriptionBuilder() diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/TimerUtil.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/TimerUtil.java index d01782f649a..54c7df5b237 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/TimerUtil.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/TimerUtil.java @@ -29,9 +29,11 @@ import org.flowable.bpmn.model.IntermediateCatchEvent; import org.flowable.bpmn.model.TimerEventDefinition; import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.definition.DefinitionVariableContainer; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.common.engine.impl.calendar.BusinessCalendar; import org.flowable.common.engine.impl.calendar.CycleBusinessCalendar; import org.flowable.common.engine.impl.calendar.DueDateBusinessCalendar; @@ -47,7 +49,6 @@ import org.flowable.job.service.event.impl.FlowableJobEventBuilder; import org.flowable.job.service.impl.persistence.entity.JobEntity; import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; -import org.flowable.variable.api.delegate.VariableScope; import org.flowable.variable.service.impl.el.NoExecutionVariableScope; import org.joda.time.DateTime; @@ -58,13 +59,69 @@ public class TimerUtil { /** * The event definition on which the timer is based. - * + *

+ * Takes in a {@link DefinitionVariableContainer} for expression evaluation at deployment time (eg Timer start event). + */ + public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEventDefinition timerEventDefinition, + FlowElement currentFlowElement, boolean isInterruptingTimer, DefinitionVariableContainer definitionVariableContainer, + String jobHandlerType, String jobHandlerConfig) { + + TimerJobEntity timer = createTimerEntity(timerEventDefinition, currentFlowElement, isInterruptingTimer, + definitionVariableContainer, jobHandlerType, jobHandlerConfig); + + if (timer != null && definitionVariableContainer != null) { + timer.setProcessDefinitionId(definitionVariableContainer.getDefinitionId()); + + if (definitionVariableContainer.getTenantId() != null) { + timer.setTenantId(definitionVariableContainer.getTenantId()); + } + } + + return timer; + } + + /** + * The event definition on which the timer is based. + *

* Takes in an optional execution, if missing the {@link NoExecutionVariableScope} will be used (eg Timer start event) */ - public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEventDefinition timerEventDefinition, + public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEventDefinition timerEventDefinition, FlowElement currentFlowElement, boolean isInterruptingTimer, ExecutionEntity executionEntity, String jobHandlerType, String jobHandlerConfig) { + VariableContainer variableContainer = executionEntity; + if (variableContainer == null) { + variableContainer = NoExecutionVariableScope.getSharedInstance(); + } + + TimerJobEntity timer = createTimerEntity(timerEventDefinition, currentFlowElement, isInterruptingTimer, + variableContainer, jobHandlerType, jobHandlerConfig); + + if (timer != null && executionEntity != null) { + timer.setExecutionId(executionEntity.getId()); + timer.setProcessDefinitionId(executionEntity.getProcessDefinitionId()); + timer.setProcessInstanceId(executionEntity.getProcessInstanceId()); + timer.setElementId(executionEntity.getCurrentFlowElement().getId()); + timer.setElementName(executionEntity.getCurrentFlowElement().getName()); + + if (executionEntity.getTenantId() != null) { + timer.setTenantId(executionEntity.getTenantId()); + } + + // ACT-1951: intermediate catching timer events shouldn't repeat according to spec + FlowElement currentElement = executionEntity.getCurrentFlowElement(); + if (currentElement instanceof IntermediateCatchEvent) { + timer.setRepeat(null); + } + } + + return timer; + } + + private static TimerJobEntity createTimerEntity(TimerEventDefinition timerEventDefinition, + FlowElement currentFlowElement, boolean isInterruptingTimer, VariableContainer variableContainer, + String jobHandlerType, String jobHandlerConfig) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); String businessCalendarRef = null; @@ -73,10 +130,6 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent // ACT-1415: timer-declaration on start-event may contain expressions NOT // evaluating variables but other context, evaluating should happen nevertheless - VariableScope scopeForExpression = executionEntity; - if (scopeForExpression == null) { - scopeForExpression = NoExecutionVariableScope.getSharedInstance(); - } if (StringUtils.isNotEmpty(timerEventDefinition.getTimeDate())) { @@ -97,11 +150,12 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent if (StringUtils.isNotEmpty(timerEventDefinition.getCalendarName())) { businessCalendarRef = timerEventDefinition.getCalendarName(); Expression businessCalendarExpression = expressionManager.createExpression(businessCalendarRef); - businessCalendarRef = businessCalendarExpression.getValue(scopeForExpression).toString(); + businessCalendarRef = businessCalendarExpression.getValue(variableContainer).toString(); } if (expression == null) { - throw new FlowableException("Timer needs configuration (either timeDate, timeCycle or timeDuration is needed) (" + timerEventDefinition.getId() + ")"); + throw new FlowableException( + "Timer needs configuration (either timeDate, timeCycle or timeDuration is needed) (" + timerEventDefinition.getId() + ")"); } BusinessCalendar businessCalendar = processEngineConfiguration.getBusinessCalendarManager().getBusinessCalendar(businessCalendarRef); @@ -109,7 +163,7 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent String dueDateString = null; Date duedate = null; - Object dueDateValue = expression.getValue(scopeForExpression); + Object dueDateValue = expression.getValue(variableContainer); if (dueDateValue instanceof String) { dueDateString = (String) dueDateValue; @@ -119,16 +173,16 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent } else if (dueDateValue instanceof DateTime) { JodaDeprecationLogger.LOGGER.warn( "Using Joda-Time DateTime has been deprecated and will be removed in a future version. Timer event listener expression {} in {} resolved to a Joda-Time DateTime. ", - expression.getExpressionText(), scopeForExpression); + expression.getExpressionText(), variableContainer); // JodaTime support duedate = ((DateTime) dueDateValue).toDate(); } else if (dueDateValue instanceof Duration) { - dueDateString = ((Duration) dueDateValue).toString(); + dueDateString = ((Duration) dueDateValue).toString(); } else if (dueDateValue instanceof Instant) { duedate = Date.from((Instant) dueDateValue); - + } else if (dueDateValue instanceof LocalDate) { duedate = Date.from(((LocalDate) dueDateValue).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()); @@ -136,8 +190,8 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent duedate = Date.from(((LocalDateTime) dueDateValue).atZone(ZoneId.systemDefault()).toInstant()); } else if (dueDateValue != null) { - throw new FlowableException("Timer '" + executionEntity.getActivityId() - + "' in " + executionEntity + " was not configured with a valid duration/time, either hand in a java.util.Date, java.time.LocalDate, java.time.LocalDateTime or a java.time.Instant or a org.joda.time.DateTime or a String in format 'yyyy-MM-dd'T'hh:mm:ss'"); + throw new FlowableException( + "Timer for " + variableContainer + " was not configured with a valid duration/time, either hand in a java.util.Date, java.time.LocalDate, java.time.LocalDateTime or a java.time.Instant or a org.joda.time.DateTime or a String in format 'yyyy-MM-dd'T'hh:mm:ss'"); } if (duedate == null && dueDateString != null) { @@ -146,16 +200,7 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent TimerJobEntity timer = null; if (duedate != null) { - - String jobCategoryElementText = null; - List jobCategoryElements = currentFlowElement.getExtensionElements().get("jobCategory"); - if (jobCategoryElements != null && jobCategoryElements.size() > 0) { - ExtensionElement jobCategoryElement = jobCategoryElements.get(0); - if (StringUtils.isNotEmpty(jobCategoryElement.getElementText())) { - jobCategoryElementText = jobCategoryElement.getElementText(); - } - } - + timer = processEngineConfiguration.getJobServiceConfiguration().getTimerJobService().createTimerJob(); timer.setJobType(JobEntity.JOB_TYPE_TIMER); timer.setRevision(1); @@ -164,33 +209,19 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent timer.setExclusive(true); timer.setRetries(processEngineConfiguration.getAsyncExecutorNumberOfRetries()); timer.setDuedate(duedate); - if (executionEntity != null) { - timer.setExecutionId(executionEntity.getId()); - timer.setProcessDefinitionId(executionEntity.getProcessDefinitionId()); - timer.setProcessInstanceId(executionEntity.getProcessInstanceId()); - - // Inherit tenant identifier (if applicable) - if (executionEntity.getTenantId() != null) { - timer.setTenantId(executionEntity.getTenantId()); - } - - if (jobCategoryElementText != null) { - Expression categoryExpression = processEngineConfiguration.getExpressionManager().createExpression(jobCategoryElementText); - Object categoryValue = categoryExpression.getValue(executionEntity); - if (categoryValue != null) { - timer.setCategory(categoryValue.toString()); - } + + String jobCategoryElementText = resolveJobCategoryText(currentFlowElement); + if (jobCategoryElementText != null) { + Expression categoryExpression = processEngineConfiguration.getExpressionManager().createExpression(jobCategoryElementText); + Object categoryValue = categoryExpression.getValue(variableContainer); + if (categoryValue != null) { + timer.setCategory(categoryValue.toString()); } - - } else if (jobCategoryElementText != null) { - timer.setCategory(jobCategoryElementText); } - + } else { StringBuilder sb = new StringBuilder("Due date could not be determined for timer job ").append(dueDateString); - if (executionEntity != null) { - sb.append(" for ").append(executionEntity); - } + sb.append(" for ").append(variableContainer); throw new FlowableException(sb.toString()); } @@ -198,36 +229,26 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent // See ACT-1427: A boundary timer with a cancelActivity='true', doesn't need to repeat itself boolean repeat = !isInterruptingTimer; - // ACT-1951: intermediate catching timer events shouldn't repeat according to spec - if (executionEntity != null) { - FlowElement currentElement = executionEntity.getCurrentFlowElement(); - if (currentElement instanceof IntermediateCatchEvent) { - repeat = false; - } - } - if (repeat) { String prepared = prepareRepeat(dueDateString); timer.setRepeat(prepared); } } - if (timer != null && executionEntity != null) { - timer.setExecutionId(executionEntity.getId()); - timer.setProcessDefinitionId(executionEntity.getProcessDefinitionId()); - timer.setProcessInstanceId(executionEntity.getProcessInstanceId()); - timer.setElementId(executionEntity.getCurrentFlowElement().getId()); - timer.setElementName(executionEntity.getCurrentFlowElement().getName()); - - // Inherit tenant identifier (if applicable) - if (executionEntity.getTenantId() != null) { - timer.setTenantId(executionEntity.getTenantId()); + return timer; + } + + private static String resolveJobCategoryText(FlowElement flowElement) { + List jobCategoryElements = flowElement.getExtensionElements().get("jobCategory"); + if (jobCategoryElements != null && jobCategoryElements.size() > 0) { + ExtensionElement jobCategoryElement = jobCategoryElements.get(0); + if (StringUtils.isNotEmpty(jobCategoryElement.getElementText())) { + return jobCategoryElement.getElementText(); } } - - return timer; + return null; } - + public static TimerJobEntity rescheduleTimerJob(String timerJobId, TimerEventDefinition timerEventDefinition) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); TimerJobService timerJobService = processEngineConfiguration.getJobServiceConfiguration().getTimerJobService(); @@ -241,7 +262,7 @@ public static TimerJobEntity rescheduleTimerJob(String timerJobId, TimerEventDef } ExecutionEntity execution = processEngineConfiguration.getExecutionEntityManager().findById(timerJob.getExecutionId()); - TimerJobEntity rescheduledTimerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, + TimerJobEntity rescheduledTimerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, eventElement, isInterruptingTimer, execution, timerJob.getJobHandlerType(), timerJob.getJobHandlerConfiguration()); @@ -250,9 +271,9 @@ public static TimerJobEntity rescheduleTimerJob(String timerJobId, TimerEventDef FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); if (eventDispatcher != null && eventDispatcher.isEnabled()) { - eventDispatcher.dispatchEvent(FlowableEventBuilder.createJobRescheduledEvent(FlowableEngineEventType.JOB_RESCHEDULED, + eventDispatcher.dispatchEvent(FlowableEventBuilder.createJobRescheduledEvent(FlowableEngineEventType.JOB_RESCHEDULED, rescheduledTimerJob, timerJob.getId()), processEngineConfiguration.getEngineCfgKey()); - + // job rescheduled event should occur before new timer scheduled event eventDispatcher.dispatchEvent(FlowableJobEventBuilder.createEntityEvent(FlowableEngineEventType.TIMER_SCHEDULED, rescheduledTimerJob), processEngineConfiguration.getEngineCfgKey()); diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java new file mode 100644 index 00000000000..a15609404d5 --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java @@ -0,0 +1,177 @@ +/* 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.bpmn.event; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.List; + +import org.flowable.engine.impl.test.PluggableFlowableTestCase; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.test.Deployment; +import org.flowable.eventsubscription.api.EventSubscription; +import org.flowable.job.api.Job; +import org.junit.jupiter.api.Test; + +/** + * Tests that signal, message, and timer start events with expressions are correctly evaluated + * at deployment time using a {@link org.flowable.common.engine.api.definition.DefinitionVariableContainer}. + * + * @author Christopher Welsch + */ +public class StartEventExpressionWithDefinitionContainerTest extends PluggableFlowableTestCase { + + @Test + @Deployment + public void testSignalStartEventExpressionResolvesAtDeployment() { + // The signal expression ${'my'}${'Signal'} should be evaluated at deployment time + // and create a signal event subscription with name "mySignal" + List subscriptions = runtimeService.createEventSubscriptionQuery() + .eventType("signal") + .list(); + assertThat(subscriptions).hasSize(1); + assertThat(subscriptions.get(0).getEventName()).isEqualTo("mySignal"); + + // Verify that sending the signal creates a process instance + runtimeService.signalEventReceived("mySignal"); + List instances = runtimeService.createProcessInstanceQuery() + .processDefinitionKey("signalExpressionProcess") + .list(); + assertThat(instances).hasSize(1); + } + + @Test + @Deployment + public void testMessageStartEventExpressionResolvesAtDeployment() { + // The message expression ${'my'}${'Message'} should be evaluated at deployment time + // and create a message event subscription with name "myMessage" + List subscriptions = runtimeService.createEventSubscriptionQuery() + .eventType("message") + .list(); + assertThat(subscriptions).hasSize(1); + assertThat(subscriptions.get(0).getEventName()).isEqualTo("myMessage"); + + // Verify that sending the message creates a process instance + ProcessInstance instance = runtimeService.startProcessInstanceByMessage("myMessage"); + assertThat(instance).isNotNull(); + assertThat(instance.getProcessDefinitionKey()).isEqualTo("messageExpressionProcess"); + } + + @Test + @Deployment + public void testTimerStartEventExpressionResolvesAtDeployment() { + try { + // The timer expression PT1H should be evaluated at deployment time + // and create a timer job + List timerJobs = managementService.createTimerJobQuery().list(); + assertThat(timerJobs).hasSize(1); + + Job timerJob = timerJobs.get(0); + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionKey("timerExpressionProcess") + .singleResult(); + assertThat(timerJob.getProcessDefinitionId()).isEqualTo(processDefinition.getId()); + + // Move clock past the timer and execute it + Date futureDate = Date.from(Instant.now().plus(2, ChronoUnit.HOURS)); + processEngineConfiguration.getClock().setCurrentTime(futureDate); + waitForJobExecutorToProcessAllJobs(5000L, 200L); + + // Verify a process instance was created + List instances = runtimeService.createProcessInstanceQuery() + .processDefinitionKey("timerExpressionProcess") + .list(); + assertThat(instances).hasSize(1); + + } finally { + processEngineConfiguration.resetClock(); + } + } + + @Test + public void testSignalStartEventExpressionWithTenantId() { + // Deploy the same signal expression process with a tenant ID + String deploymentId = repositoryService.createDeployment() + .addClasspathResource( + "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml") + .tenantId("tenantA") + .deploy() + .getId(); + + try { + List subscriptions = runtimeService.createEventSubscriptionQuery() + .eventType("signal") + .tenantId("tenantA") + .list(); + assertThat(subscriptions).hasSize(1); + assertThat(subscriptions.get(0).getEventName()).isEqualTo("mySignal"); + assertThat(subscriptions.get(0).getTenantId()).isEqualTo("tenantA"); + } finally { + repositoryService.deleteDeployment(deploymentId, true); + } + } + + @Test + public void testMessageStartEventExpressionWithTenantId() { + // Deploy the same message expression process with a tenant ID + String deploymentId = repositoryService.createDeployment() + .addClasspathResource( + "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml") + .tenantId("tenantA") + .deploy() + .getId(); + + try { + List subscriptions = runtimeService.createEventSubscriptionQuery() + .eventType("message") + .tenantId("tenantA") + .list(); + assertThat(subscriptions).hasSize(1); + assertThat(subscriptions.get(0).getEventName()).isEqualTo("myMessage"); + assertThat(subscriptions.get(0).getTenantId()).isEqualTo("tenantA"); + } finally { + repositoryService.deleteDeployment(deploymentId, true); + } + } + + @Test + public void testTimerStartEventExpressionWithTenantId() { + // Deploy the same timer expression process with a tenant ID + String deploymentId = repositoryService.createDeployment() + .addClasspathResource( + "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml") + .tenantId("tenantA") + .deploy() + .getId(); + + try { + List timerJobs = managementService.createTimerJobQuery() + .jobTenantId("tenantA") + .list(); + assertThat(timerJobs).hasSize(1); + assertThat(timerJobs.get(0).getTenantId()).isEqualTo("tenantA"); + + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionKey("timerExpressionProcess") + .processDefinitionTenantId("tenantA") + .singleResult(); + assertThat(timerJobs.get(0).getProcessDefinitionId()).isEqualTo(processDefinition.getId()); + } finally { + repositoryService.deleteDeployment(deploymentId, true); + } + } +} diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/el/DefinitionVariableContainerTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/el/DefinitionVariableContainerTest.java new file mode 100644 index 00000000000..10fafcbb6b3 --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/el/DefinitionVariableContainerTest.java @@ -0,0 +1,64 @@ +/* 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.el; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.flowable.common.engine.api.definition.DefinitionVariableContainer; +import org.flowable.common.engine.api.variable.VariableContainer; +import org.junit.jupiter.api.Test; + +/** + * @author Christopher Welsch + */ +class DefinitionVariableContainerTest { + + @Test + void testDefaultConstructor() { + DefinitionVariableContainer container = new DefinitionVariableContainer(); + assertThat(container.getDefinitionId()).isNull(); + assertThat(container.getDeploymentId()).isNull(); + assertThat(container.getScopeType()).isNull(); + assertThat(container.getTenantId()).isNull(); + } + + @Test + void testParameterizedConstructor() { + DefinitionVariableContainer container = new DefinitionVariableContainer("defId", "deplId", "bpmn", "tenantA"); + assertThat(container.getDefinitionId()).isEqualTo("defId"); + assertThat(container.getDeploymentId()).isEqualTo("deplId"); + assertThat(container.getScopeType()).isEqualTo("bpmn"); + assertThat(container.getTenantId()).isEqualTo("tenantA"); + } + + @Test + void testImplementsVariableContainer() { + DefinitionVariableContainer container = new DefinitionVariableContainer("defId", "deplId", "bpmn", "tenantA"); + assertThat(container).isInstanceOf(VariableContainer.class); + } + + @Test + void testVariableContainerBehavior() { + DefinitionVariableContainer container = new DefinitionVariableContainer("defId", "deplId", "bpmn", "tenantA"); + + // DefinitionVariableContainer does not hold variables + assertThat(container.hasVariable("anyVar")).isFalse(); + assertThat(container.getVariable("anyVar")).isNull(); + assertThat(container.getVariableNames()).isEmpty(); + + // setVariable and setTransientVariable should not throw + container.setVariable("test", "value"); + container.setTransientVariable("test", "value"); + assertThat(container.hasVariable("test")).isFalse(); + } +} diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml new file mode 100644 index 00000000000..40485eed7ef --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml new file mode 100644 index 00000000000..c71c0c7f7f4 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml new file mode 100644 index 00000000000..ac4e9f65d3b --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml @@ -0,0 +1,41 @@ + + + + + + + PT1H + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From cca37613fd27054d85137834f665236eec4ec1f4 Mon Sep 17 00:00:00 2001 From: Christopher Welsch Date: Tue, 17 Feb 2026 08:29:56 +0100 Subject: [PATCH 2/3] changed copyright header --- .../definition/DefinitionVariableContainer.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java b/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java index c27781d44e4..1f3571bfac5 100644 --- a/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java +++ b/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java @@ -1,9 +1,14 @@ -/* - * Copyright 2026, Flowable Licences AG. - * This license is based on the software license agreement and terms and conditions in effect between the parties - * at the time of purchase of the Flowable software product. - * Your agreement to these terms and conditions is required to install or use the Flowable software product and/or this file. - * Flowable is a trademark of Flowable AG registered in several countries. +/* 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.api.definition; From bd52b75d8e96bace984d1bf3ab40edab26abe50e Mon Sep 17 00:00:00 2001 From: Christopher Welsch Date: Tue, 17 Feb 2026 16:01:20 +0100 Subject: [PATCH 3/3] changes based on comments --- .../impl/el}/DefinitionVariableContainer.java | 15 +- .../deployer/EventSubscriptionManager.java | 12 +- .../impl/bpmn/deployer/TimerManager.java | 13 +- .../dynamic/AbstractDynamicStateManager.java | 7 +- .../event/EventDefinitionExpressionUtil.java | 76 ++++----- ...tProcessDefinitionDeletionManagerImpl.java | 32 +--- .../impl/util/ProcessInstanceHelper.java | 10 +- .../flowable/engine/impl/util/TimerUtil.java | 33 ++-- ...ExpressionWithDefinitionContainerTest.java | 153 +++++++----------- .../el/DefinitionVariableContainerTest.java | 64 -------- ...tExpressionResolvesAtDeployment.bpmn20.xml | 2 +- ...tExpressionResolvesAtDeployment.bpmn20.xml | 2 +- ...tExpressionResolvesAtDeployment.bpmn20.xml | 3 +- 13 files changed, 137 insertions(+), 285 deletions(-) rename modules/{flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition => flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el}/DefinitionVariableContainer.java (86%) delete mode 100644 modules/flowable-engine/src/test/java/org/flowable/engine/test/el/DefinitionVariableContainerTest.java diff --git a/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/DefinitionVariableContainer.java similarity index 86% rename from modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java rename to modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/DefinitionVariableContainer.java index 1f3571bfac5..56de1aa73be 100644 --- a/modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api/definition/DefinitionVariableContainer.java +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/DefinitionVariableContainer.java @@ -10,14 +10,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.flowable.common.engine.api.definition; +package org.flowable.common.engine.impl.el; import java.util.Set; import org.flowable.common.engine.api.variable.VariableContainer; /** - * A {@link VariableContainer} that carries definition context (definitionId, deploymentId, scopeType, tenantId) + * A {@link VariableContainer} that carries definition context (definitionId, definitionKey, deploymentId, scopeType, tenantId) * for expression evaluation when no execution is active (e.g. during deployment). * This allows EL resolvers to look up the parent deployment and resolve definition-scoped expressions. * @@ -26,15 +26,14 @@ public class DefinitionVariableContainer implements VariableContainer { protected String definitionId; + protected String definitionKey; protected String deploymentId; protected String scopeType; protected String tenantId; - public DefinitionVariableContainer() { - } - - public DefinitionVariableContainer(String definitionId, String deploymentId, String scopeType, String tenantId) { + public DefinitionVariableContainer(String definitionId, String definitionKey, String deploymentId, String scopeType, String tenantId) { this.definitionId = definitionId; + this.definitionKey = definitionKey; this.deploymentId = deploymentId; this.scopeType = scopeType; this.tenantId = tenantId; @@ -44,6 +43,10 @@ public String getDefinitionId() { return definitionId; } + public String getDefinitionKey() { + return definitionKey; + } + public String getDeploymentId() { return deploymentId; } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/EventSubscriptionManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/EventSubscriptionManager.java index e2ea5f0c7bb..bd3403cfd26 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/EventSubscriptionManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/EventSubscriptionManager.java @@ -45,7 +45,7 @@ import org.flowable.eventsubscription.service.impl.persistence.entity.EventSubscriptionEntity; import org.flowable.eventsubscription.service.impl.persistence.entity.MessageEventSubscriptionEntity; import org.flowable.eventsubscription.service.impl.persistence.entity.SignalEventSubscriptionEntity; -import org.flowable.common.engine.api.definition.DefinitionVariableContainer; +import org.flowable.common.engine.impl.el.DefinitionVariableContainer; /** * Manages event subscriptions for newly-deployed process definitions and their previous versions. @@ -174,10 +174,7 @@ protected void insertSignalEvent(SignalEventDefinition signalEventDefinition, St EventSubscriptionService eventSubscriptionService = processEngineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService(); SignalEventSubscriptionEntity subscriptionEntity = eventSubscriptionService.createSignalEventSubscription(); - DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), processDefinition.getDeploymentId(), - ScopeTypes.BPMN, processDefinition.getTenantId()); - - String signalName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, definitionVariableContainer); + String signalName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, processDefinition); subscriptionEntity.setEventName(signalName); subscriptionEntity.setActivityId(startEvent.getId()); @@ -196,11 +193,8 @@ protected void insertMessageEvent(MessageEventDefinition messageEventDefinition, ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); EventSubscriptionService eventSubscriptionService = processEngineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService(); - DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), processDefinition.getDeploymentId(), - ScopeTypes.BPMN, processDefinition.getTenantId()); - // look for subscriptions for the same name in db: - String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); + String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, processDefinition); List subscriptionsForSameMessageName = eventSubscriptionService .findEventSubscriptionsByName(MessageEventHandler.EVENT_HANDLER_TYPE, messageName, processDefinition.getTenantId()); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/TimerManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/TimerManager.java index aeee036f5d4..5db18d6fde3 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/TimerManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/TimerManager.java @@ -20,7 +20,6 @@ import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.StartEvent; import org.flowable.bpmn.model.TimerEventDefinition; -import org.flowable.common.engine.api.scope.ScopeTypes; import org.flowable.common.engine.impl.context.Context; import org.flowable.common.engine.impl.util.CollectionUtil; import org.flowable.engine.ProcessEngineConfiguration; @@ -33,7 +32,6 @@ import org.flowable.job.service.TimerJobService; import org.flowable.job.service.impl.cmd.CancelJobsCmd; import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; -import org.flowable.common.engine.api.definition.DefinitionVariableContainer; /** * Manages timers for newly-deployed process definitions and their previous versions. @@ -76,19 +74,10 @@ protected List getTimerDeclarations(ProcessDefinitionEntity proc EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0); if (eventDefinition instanceof TimerEventDefinition timerEventDefinition) { - DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), - processDefinition.getDeploymentId(), - ScopeTypes.BPMN, processDefinition.getTenantId()); - TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, startEvent, - false, definitionVariableContainer, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), + false, processDefinition, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); - timerJob.setProcessDefinitionId(processDefinition.getId()); - - if (processDefinition.getTenantId() != null) { - timerJob.setTenantId(processDefinition.getTenantId()); - } timers.add(timerJob); } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java index d6a2b77dbd7..6ef86924dd6 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/AbstractDynamicStateManager.java @@ -55,7 +55,7 @@ import org.flowable.bpmn.model.UserTask; import org.flowable.bpmn.model.ValuedDataObject; import org.flowable.common.engine.api.FlowableException; -import org.flowable.common.engine.api.definition.DefinitionVariableContainer; +import org.flowable.common.engine.impl.el.DefinitionVariableContainer; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; @@ -1587,7 +1587,8 @@ protected void processCreatedEventSubProcess(EventSubProcess eventSubProcess, Ex messageExecution.setActive(false); DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(messageExecution.getProcessDefinitionId(), - eventSubProcessExecution.getDeploymentId(), ScopeTypes.BPMN, messageExecution.getTenantId()); + messageExecution.getProcessDefinitionKey(), eventSubProcessExecution.getDeploymentId(), ScopeTypes.BPMN, messageExecution.getTenantId()); + String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); EventSubscriptionEntity messageSubscription = (EventSubscriptionEntity) eventSubscriptionService.createEventSubscriptionBuilder() .eventType(MessageEventSubscriptionEntity.EVENT_TYPE) @@ -1622,7 +1623,7 @@ protected void processCreatedEventSubProcess(EventSubProcess eventSubProcess, Ex signalExecution.setActive(false); DefinitionVariableContainer signalDefinitionVariableContainer = new DefinitionVariableContainer(signalExecution.getProcessDefinitionId(), - eventSubProcessExecution.getDeploymentId(), ScopeTypes.BPMN, signalExecution.getTenantId()); + signalExecution.getProcessDefinitionKey(), eventSubProcessExecution.getDeploymentId(), ScopeTypes.BPMN, signalExecution.getTenantId()); String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, signalDefinitionVariableContainer); EventSubscriptionEntity signalSubscription = (EventSubscriptionEntity) eventSubscriptionService.createEventSubscriptionBuilder() diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/EventDefinitionExpressionUtil.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/EventDefinitionExpressionUtil.java index 50798299075..333b62bf686 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/EventDefinitionExpressionUtil.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/event/EventDefinitionExpressionUtil.java @@ -18,10 +18,12 @@ import org.flowable.bpmn.model.Signal; import org.flowable.bpmn.model.SignalEventDefinition; import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.common.engine.api.scope.ScopeTypes; +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.common.engine.impl.el.DefinitionVariableContainer; import org.flowable.common.engine.impl.interceptor.CommandContext; -import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.impl.util.CommandContextUtil; -import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.engine.repository.ProcessDefinition; import org.flowable.variable.service.impl.el.NoExecutionVariableScope; /** @@ -35,36 +37,24 @@ public class EventDefinitionExpressionUtil { * - otherwise, the signal ref is used * - unless a signalExpression is set */ - public static String determineSignalName(CommandContext commandContext, SignalEventDefinition signalEventDefinition, BpmnModel bpmnModel, DelegateExecution execution) { - String signalName = null; - if (StringUtils.isNotEmpty(signalEventDefinition.getSignalRef())) { - Signal signal = bpmnModel.getSignal(signalEventDefinition.getSignalRef()); - if (signal != null) { - signalName = signal.getName(); - } else { - signalName = signalEventDefinition.getSignalRef(); - } + public static String determineSignalName(CommandContext commandContext, SignalEventDefinition signalEventDefinition, + BpmnModel bpmnModel, ProcessDefinition processDefinition) { - } else { - signalName = signalEventDefinition.getSignalExpression(); + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), + processDefinition.getKey(), processDefinition.getDeploymentId(), ScopeTypes.BPMN, processDefinition.getTenantId()); - } - - if (StringUtils.isNotEmpty(signalName)) { - Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(signalName); - return expression.getValue(execution != null ? execution : NoExecutionVariableScope.getSharedInstance()).toString(); - } - - return signalName; + return determineSignalName(commandContext, signalEventDefinition, bpmnModel, definitionVariableContainer); } /** - * Determines signal name from definition or expression + * Determines the signal name of the {@link SignalEventDefinition} that is passed: + * - if a signal name is set, it has precedence + * - otherwise, the signal ref is used + * - unless a signalExpression is set */ - public static String determineSignalName(CommandContext commandContext, SignalEventDefinition signalEventDefinition, BpmnModel bpmnModel, - VariableContainer definitionVariableContainer) { + public static String determineSignalName(CommandContext commandContext, SignalEventDefinition signalEventDefinition, + BpmnModel bpmnModel, VariableContainer variableContainer) { String signalName = null; - if (StringUtils.isNotEmpty(signalEventDefinition.getSignalRef())) { Signal signal = bpmnModel.getSignal(signalEventDefinition.getSignalRef()); if (signal != null) { @@ -80,7 +70,7 @@ public static String determineSignalName(CommandContext commandContext, SignalEv if (StringUtils.isNotEmpty(signalName)) { Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(signalName); - return expression.getValue(definitionVariableContainer).toString(); + return expression.getValue(variableContainer != null ? variableContainer : NoExecutionVariableScope.getSharedInstance()).toString(); } return signalName; @@ -90,35 +80,29 @@ public static String determineSignalName(CommandContext commandContext, SignalEv * Determines the event name of the {@link org.flowable.bpmn.model.MessageEventDefinition} that is passed: * - if a message ref is set, it has precedence * - if a messageExpression is set, it is returned - * + *

* Note that, contrary to the determineSignalName method, the name of the message is never used. * This is because of historical reasons (and it can't be changed now without breaking existing models/instances) */ - public static String determineMessageName(CommandContext commandContext, MessageEventDefinition messageEventDefinition, DelegateExecution execution) { - String messageName = null; - if (StringUtils.isNotEmpty(messageEventDefinition.getMessageRef())) { - return messageEventDefinition.getMessageRef(); - - } else { - messageName = messageEventDefinition.getMessageExpression(); + public static String determineMessageName(CommandContext commandContext, MessageEventDefinition messageEventDefinition, + ProcessDefinition processDefinition) { - } + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), + processDefinition.getKey(), processDefinition.getDeploymentId(), ScopeTypes.BPMN, processDefinition.getTenantId()); - if (StringUtils.isNotEmpty(messageName)) { - Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(messageName); - return expression.getValue(execution != null ? execution : NoExecutionVariableScope.getSharedInstance()).toString(); - } - - return messageName; + return determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); } - /** - * Determines message name from definition or expression + * Determines the event name of the {@link org.flowable.bpmn.model.MessageEventDefinition} that is passed: + * - if a message ref is set, it has precedence + * - if a messageExpression is set, it is returned + * + * Note that, contrary to the determineSignalName method, the name of the message is never used. + * This is because of historical reasons (and it can't be changed now without breaking existing models/instances) */ public static String determineMessageName(CommandContext commandContext, MessageEventDefinition messageEventDefinition, - VariableContainer definitionVariableContainer) { + VariableContainer variableContainer) { String messageName = null; - if (StringUtils.isNotEmpty(messageEventDefinition.getMessageRef())) { return messageEventDefinition.getMessageRef(); @@ -129,7 +113,7 @@ public static String determineMessageName(CommandContext commandContext, Message if (StringUtils.isNotEmpty(messageName)) { Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(messageName); - return expression.getValue(definitionVariableContainer).toString(); + return expression.getValue(variableContainer != null ? variableContainer : NoExecutionVariableScope.getSharedInstance()).toString(); } return messageName; diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/repository/DeploymentProcessDefinitionDeletionManagerImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/repository/DeploymentProcessDefinitionDeletionManagerImpl.java index a6349709551..28eda80328a 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/repository/DeploymentProcessDefinitionDeletionManagerImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/repository/DeploymentProcessDefinitionDeletionManagerImpl.java @@ -26,6 +26,7 @@ import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; import org.flowable.common.engine.api.scope.ScopeTypes; +import org.flowable.common.engine.impl.el.DefinitionVariableContainer; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.common.engine.impl.util.CollectionUtil; import org.flowable.engine.ProcessEngineConfiguration; @@ -36,6 +37,7 @@ import org.flowable.engine.impl.event.EventDefinitionExpressionUtil; import org.flowable.engine.impl.jobexecutor.TimerEventHandler; import org.flowable.engine.impl.jobexecutor.TimerStartEventJobHandler; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityManager; import org.flowable.engine.impl.util.CorrelationUtil; @@ -50,7 +52,6 @@ import org.flowable.eventsubscription.service.impl.persistence.entity.SignalEventSubscriptionEntity; import org.flowable.job.service.TimerJobService; import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; -import org.flowable.common.engine.api.definition.DefinitionVariableContainer; /** * @author Filip Hrisafov @@ -156,26 +157,11 @@ protected void restorePreviousStartEventsIfNeeded(ProcessDefinition processDefin protected void restoreTimerStartEvent(ProcessDefinition previousProcessDefinition, StartEvent startEvent, EventDefinition eventDefinition) { TimerEventDefinition timerEventDefinition = (TimerEventDefinition) eventDefinition; - DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(previousProcessDefinition.getId(), - previousProcessDefinition.getDeploymentId(), ScopeTypes.BPMN, previousProcessDefinition.getTenantId()); - - TimerJobEntity timer = TimerUtil.createTimerEntityForTimerEventDefinition((TimerEventDefinition) eventDefinition, startEvent, - false, definitionVariableContainer, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), + TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, startEvent, + false, previousProcessDefinition, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); - if (timer != null) { - TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, startEvent, - false, definitionVariableContainer, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), - timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); - - timerJob.setProcessDefinitionId(previousProcessDefinition.getId()); - - if (previousProcessDefinition.getTenantId() != null) { - timerJob.setTenantId(previousProcessDefinition.getTenantId()); - } - - engineConfiguration.getJobServiceConfiguration().getTimerJobService().scheduleTimerJob(timerJob); - } + engineConfiguration.getJobServiceConfiguration().getTimerJobService().scheduleTimerJob(timerJob); } protected void restoreSignalStartEvent(ProcessDefinition previousProcessDefinition, BpmnModel bpmnModel, StartEvent startEvent, EventDefinition eventDefinition) { @@ -183,9 +169,7 @@ protected void restoreSignalStartEvent(ProcessDefinition previousProcessDefiniti SignalEventDefinition signalEventDefinition = (SignalEventDefinition) eventDefinition; SignalEventSubscriptionEntity subscriptionEntity = engineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService().createSignalEventSubscription(); - DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(previousProcessDefinition.getId(), - previousProcessDefinition.getDeploymentId(), ScopeTypes.BPMN, previousProcessDefinition.getTenantId()); - String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, definitionVariableContainer); + String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, previousProcessDefinition); subscriptionEntity.setEventName(eventName); subscriptionEntity.setActivityId(startEvent.getId()); subscriptionEntity.setProcessDefinitionId(previousProcessDefinition.getId()); @@ -206,9 +190,7 @@ protected void restoreMessageStartEvent(ProcessDefinition previousProcessDefinit CommandContext commandContext = Context.getCommandContext(); MessageEventSubscriptionEntity newSubscription = engineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService().createMessageEventSubscription(); - DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(previousProcessDefinition.getId(), - previousProcessDefinition.getDeploymentId(), ScopeTypes.BPMN, previousProcessDefinition.getTenantId()); - String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); + String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, previousProcessDefinition); newSubscription.setEventName(messageName); newSubscription.setActivityId(startEvent.getId()); newSubscription.setConfiguration(previousProcessDefinition.getId()); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessInstanceHelper.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessInstanceHelper.java index 5c5c1bde947..fbc1a5792aa 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessInstanceHelper.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessInstanceHelper.java @@ -64,7 +64,7 @@ import org.flowable.eventsubscription.service.impl.persistence.entity.SignalEventSubscriptionEntity; import org.flowable.identitylink.api.IdentityLinkType; import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; -import org.flowable.common.engine.api.definition.DefinitionVariableContainer; +import org.flowable.common.engine.impl.el.DefinitionVariableContainer; import tools.jackson.databind.node.ObjectNode; @@ -157,11 +157,7 @@ public ProcessInstance createAndStartProcessInstanceByMessage(ProcessDefinition if (flowElement instanceof StartEvent startEvent) { if (CollectionUtil.isNotEmpty(startEvent.getEventDefinitions()) && startEvent.getEventDefinitions() .get(0) instanceof MessageEventDefinition messageEventDefinition) { - - DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), processDefinition.getDeploymentId(), - ScopeTypes.BPMN, processDefinition.getTenantId()); - - String actualMessageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); + String actualMessageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, processDefinition); if (Objects.equals(actualMessageName, messageName)) { initialFlowElement = flowElement; break; @@ -466,7 +462,7 @@ protected void handleSignalEventSubscription(EventDefinition eventDefinition, St signalExecution.setActive(false); DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(parentExecution.getProcessDefinitionId(), - parentExecution.getDeploymentId(), ScopeTypes.BPMN, parentExecution.getTenantId()); + parentExecution.getProcessDefinitionKey(), parentExecution.getDeploymentId(), ScopeTypes.BPMN, parentExecution.getTenantId()); String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, definitionVariableContainer); EventSubscriptionEntity eventSubscription = (EventSubscriptionEntity) processEngineConfiguration.getEventSubscriptionServiceConfiguration() diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/TimerUtil.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/TimerUtil.java index 54c7df5b237..652264f8b99 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/TimerUtil.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/TimerUtil.java @@ -29,8 +29,9 @@ import org.flowable.bpmn.model.IntermediateCatchEvent; import org.flowable.bpmn.model.TimerEventDefinition; import org.flowable.common.engine.api.FlowableException; -import org.flowable.common.engine.api.definition.DefinitionVariableContainer; import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.common.engine.api.scope.ScopeTypes; +import org.flowable.common.engine.impl.el.DefinitionVariableContainer; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; import org.flowable.common.engine.api.variable.VariableContainer; @@ -43,6 +44,7 @@ import org.flowable.common.engine.impl.runtime.Clock; import org.flowable.engine.delegate.event.impl.FlowableEventBuilder; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.impl.jobexecutor.TimerEventHandler; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.job.service.TimerJobService; @@ -60,20 +62,23 @@ public class TimerUtil { /** * The event definition on which the timer is based. *

- * Takes in a {@link DefinitionVariableContainer} for expression evaluation at deployment time (eg Timer start event). + * Takes in a {@link ProcessDefinition} for expression evaluation at deployment time (eg Timer start event). */ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEventDefinition timerEventDefinition, - FlowElement currentFlowElement, boolean isInterruptingTimer, DefinitionVariableContainer definitionVariableContainer, + FlowElement currentFlowElement, boolean isInterruptingTimer, ProcessDefinition processDefinition, String jobHandlerType, String jobHandlerConfig) { + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), + processDefinition.getKey(), processDefinition.getDeploymentId(), ScopeTypes.BPMN, processDefinition.getTenantId()); + TimerJobEntity timer = createTimerEntity(timerEventDefinition, currentFlowElement, isInterruptingTimer, definitionVariableContainer, jobHandlerType, jobHandlerConfig); - if (timer != null && definitionVariableContainer != null) { - timer.setProcessDefinitionId(definitionVariableContainer.getDefinitionId()); + if (timer != null) { + timer.setProcessDefinitionId(processDefinition.getId()); - if (definitionVariableContainer.getTenantId() != null) { - timer.setTenantId(definitionVariableContainer.getTenantId()); + if (processDefinition.getTenantId() != null) { + timer.setTenantId(processDefinition.getTenantId()); } } @@ -107,12 +112,6 @@ public static TimerJobEntity createTimerEntityForTimerEventDefinition(TimerEvent if (executionEntity.getTenantId() != null) { timer.setTenantId(executionEntity.getTenantId()); } - - // ACT-1951: intermediate catching timer events shouldn't repeat according to spec - FlowElement currentElement = executionEntity.getCurrentFlowElement(); - if (currentElement instanceof IntermediateCatchEvent) { - timer.setRepeat(null); - } } return timer; @@ -128,9 +127,6 @@ private static TimerJobEntity createTimerEntity(TimerEventDefinition timerEventD Expression expression = null; ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager(); - // ACT-1415: timer-declaration on start-event may contain expressions NOT - // evaluating variables but other context, evaluating should happen nevertheless - if (StringUtils.isNotEmpty(timerEventDefinition.getTimeDate())) { businessCalendarRef = DueDateBusinessCalendar.NAME; @@ -235,6 +231,11 @@ private static TimerJobEntity createTimerEntity(TimerEventDefinition timerEventD } } + // ACT-1951: intermediate catching timer events shouldn't repeat according to spec + if (currentFlowElement instanceof IntermediateCatchEvent) { + timer.setRepeat(null); + } + return timer; } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java index a15609404d5..338175537f7 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java @@ -29,7 +29,7 @@ /** * Tests that signal, message, and timer start events with expressions are correctly evaluated - * at deployment time using a {@link org.flowable.common.engine.api.definition.DefinitionVariableContainer}. + * at deployment time using a {@link org.flowable.common.engine.impl.el.DefinitionVariableContainer}. * * @author Christopher Welsch */ @@ -38,16 +38,16 @@ public class StartEventExpressionWithDefinitionContainerTest extends PluggableFl @Test @Deployment public void testSignalStartEventExpressionResolvesAtDeployment() { - // The signal expression ${'my'}${'Signal'} should be evaluated at deployment time - // and create a signal event subscription with name "mySignal" + // The signal expression ${variableContainer.definitionKey}Signal should be evaluated at deployment time + // and create a signal event subscription with name "signalExpressionProcessSignal" List subscriptions = runtimeService.createEventSubscriptionQuery() .eventType("signal") .list(); assertThat(subscriptions).hasSize(1); - assertThat(subscriptions.get(0).getEventName()).isEqualTo("mySignal"); + assertThat(subscriptions.get(0).getEventName()).isEqualTo("signalExpressionProcessSignal"); // Verify that sending the signal creates a process instance - runtimeService.signalEventReceived("mySignal"); + runtimeService.signalEventReceived("signalExpressionProcessSignal"); List instances = runtimeService.createProcessInstanceQuery() .processDefinitionKey("signalExpressionProcess") .list(); @@ -57,16 +57,16 @@ public void testSignalStartEventExpressionResolvesAtDeployment() { @Test @Deployment public void testMessageStartEventExpressionResolvesAtDeployment() { - // The message expression ${'my'}${'Message'} should be evaluated at deployment time - // and create a message event subscription with name "myMessage" + // The message expression ${variableContainer.definitionKey}Message should be evaluated at deployment time + // and create a message event subscription with name "messageExpressionProcessMessage" List subscriptions = runtimeService.createEventSubscriptionQuery() .eventType("message") .list(); assertThat(subscriptions).hasSize(1); - assertThat(subscriptions.get(0).getEventName()).isEqualTo("myMessage"); + assertThat(subscriptions.get(0).getEventName()).isEqualTo("messageExpressionProcessMessage"); // Verify that sending the message creates a process instance - ProcessInstance instance = runtimeService.startProcessInstanceByMessage("myMessage"); + ProcessInstance instance = runtimeService.startProcessInstanceByMessage("messageExpressionProcessMessage"); assertThat(instance).isNotNull(); assertThat(instance.getProcessDefinitionKey()).isEqualTo("messageExpressionProcess"); } @@ -74,104 +74,69 @@ public void testMessageStartEventExpressionResolvesAtDeployment() { @Test @Deployment public void testTimerStartEventExpressionResolvesAtDeployment() { - try { - // The timer expression PT1H should be evaluated at deployment time - // and create a timer job - List timerJobs = managementService.createTimerJobQuery().list(); - assertThat(timerJobs).hasSize(1); - - Job timerJob = timerJobs.get(0); - ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() - .processDefinitionKey("timerExpressionProcess") - .singleResult(); - assertThat(timerJob.getProcessDefinitionId()).isEqualTo(processDefinition.getId()); - - // Move clock past the timer and execute it - Date futureDate = Date.from(Instant.now().plus(2, ChronoUnit.HOURS)); - processEngineConfiguration.getClock().setCurrentTime(futureDate); - waitForJobExecutorToProcessAllJobs(5000L, 200L); - - // Verify a process instance was created - List instances = runtimeService.createProcessInstanceQuery() - .processDefinitionKey("timerExpressionProcess") - .list(); - assertThat(instances).hasSize(1); - - } finally { - processEngineConfiguration.resetClock(); - } + // The timer expression ${variableContainer.definitionKey == 'timerExpressionProcess' ? 'PT1H' : 'PT2H'} + // should be evaluated at deployment time and create a timer job with PT1H duration + List timerJobs = managementService.createTimerJobQuery().list(); + assertThat(timerJobs).hasSize(1); + + Job timerJob = timerJobs.get(0); + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionKey("timerExpressionProcess") + .singleResult(); + assertThat(timerJob.getProcessDefinitionId()).isEqualTo(processDefinition.getId()); + + // Move clock past the timer and execute it + Date futureDate = Date.from(Instant.now().plus(2, ChronoUnit.HOURS)); + processEngineConfiguration.getClock().setCurrentTime(futureDate); + waitForJobExecutorToProcessAllJobs(5000L, 200L); + + // Verify a process instance was created + List instances = runtimeService.createProcessInstanceQuery() + .processDefinitionKey("timerExpressionProcess") + .list(); + assertThat(instances).hasSize(1); } @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml", + tenantId = "tenantA") public void testSignalStartEventExpressionWithTenantId() { - // Deploy the same signal expression process with a tenant ID - String deploymentId = repositoryService.createDeployment() - .addClasspathResource( - "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml") + List subscriptions = runtimeService.createEventSubscriptionQuery() + .eventType("signal") .tenantId("tenantA") - .deploy() - .getId(); - - try { - List subscriptions = runtimeService.createEventSubscriptionQuery() - .eventType("signal") - .tenantId("tenantA") - .list(); - assertThat(subscriptions).hasSize(1); - assertThat(subscriptions.get(0).getEventName()).isEqualTo("mySignal"); - assertThat(subscriptions.get(0).getTenantId()).isEqualTo("tenantA"); - } finally { - repositoryService.deleteDeployment(deploymentId, true); - } + .list(); + assertThat(subscriptions).hasSize(1); + assertThat(subscriptions.get(0).getEventName()).isEqualTo("signalExpressionProcessSignal"); + assertThat(subscriptions.get(0).getTenantId()).isEqualTo("tenantA"); } @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml", + tenantId = "tenantA") public void testMessageStartEventExpressionWithTenantId() { - // Deploy the same message expression process with a tenant ID - String deploymentId = repositoryService.createDeployment() - .addClasspathResource( - "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml") + List subscriptions = runtimeService.createEventSubscriptionQuery() + .eventType("message") .tenantId("tenantA") - .deploy() - .getId(); - - try { - List subscriptions = runtimeService.createEventSubscriptionQuery() - .eventType("message") - .tenantId("tenantA") - .list(); - assertThat(subscriptions).hasSize(1); - assertThat(subscriptions.get(0).getEventName()).isEqualTo("myMessage"); - assertThat(subscriptions.get(0).getTenantId()).isEqualTo("tenantA"); - } finally { - repositoryService.deleteDeployment(deploymentId, true); - } + .list(); + assertThat(subscriptions).hasSize(1); + assertThat(subscriptions.get(0).getEventName()).isEqualTo("messageExpressionProcessMessage"); + assertThat(subscriptions.get(0).getTenantId()).isEqualTo("tenantA"); } @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml", + tenantId = "tenantA") public void testTimerStartEventExpressionWithTenantId() { - // Deploy the same timer expression process with a tenant ID - String deploymentId = repositoryService.createDeployment() - .addClasspathResource( - "org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml") - .tenantId("tenantA") - .deploy() - .getId(); - - try { - List timerJobs = managementService.createTimerJobQuery() - .jobTenantId("tenantA") - .list(); - assertThat(timerJobs).hasSize(1); - assertThat(timerJobs.get(0).getTenantId()).isEqualTo("tenantA"); - - ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() - .processDefinitionKey("timerExpressionProcess") - .processDefinitionTenantId("tenantA") - .singleResult(); - assertThat(timerJobs.get(0).getProcessDefinitionId()).isEqualTo(processDefinition.getId()); - } finally { - repositoryService.deleteDeployment(deploymentId, true); - } + List timerJobs = managementService.createTimerJobQuery() + .jobTenantId("tenantA") + .list(); + assertThat(timerJobs).hasSize(1); + assertThat(timerJobs.get(0).getTenantId()).isEqualTo("tenantA"); + + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionKey("timerExpressionProcess") + .processDefinitionTenantId("tenantA") + .singleResult(); + assertThat(timerJobs.get(0).getProcessDefinitionId()).isEqualTo(processDefinition.getId()); } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/el/DefinitionVariableContainerTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/el/DefinitionVariableContainerTest.java deleted file mode 100644 index 10fafcbb6b3..00000000000 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/el/DefinitionVariableContainerTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* 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.el; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.flowable.common.engine.api.definition.DefinitionVariableContainer; -import org.flowable.common.engine.api.variable.VariableContainer; -import org.junit.jupiter.api.Test; - -/** - * @author Christopher Welsch - */ -class DefinitionVariableContainerTest { - - @Test - void testDefaultConstructor() { - DefinitionVariableContainer container = new DefinitionVariableContainer(); - assertThat(container.getDefinitionId()).isNull(); - assertThat(container.getDeploymentId()).isNull(); - assertThat(container.getScopeType()).isNull(); - assertThat(container.getTenantId()).isNull(); - } - - @Test - void testParameterizedConstructor() { - DefinitionVariableContainer container = new DefinitionVariableContainer("defId", "deplId", "bpmn", "tenantA"); - assertThat(container.getDefinitionId()).isEqualTo("defId"); - assertThat(container.getDeploymentId()).isEqualTo("deplId"); - assertThat(container.getScopeType()).isEqualTo("bpmn"); - assertThat(container.getTenantId()).isEqualTo("tenantA"); - } - - @Test - void testImplementsVariableContainer() { - DefinitionVariableContainer container = new DefinitionVariableContainer("defId", "deplId", "bpmn", "tenantA"); - assertThat(container).isInstanceOf(VariableContainer.class); - } - - @Test - void testVariableContainerBehavior() { - DefinitionVariableContainer container = new DefinitionVariableContainer("defId", "deplId", "bpmn", "tenantA"); - - // DefinitionVariableContainer does not hold variables - assertThat(container.hasVariable("anyVar")).isFalse(); - assertThat(container.getVariable("anyVar")).isNull(); - assertThat(container.getVariableNames()).isEmpty(); - - // setVariable and setTransientVariable should not throw - container.setVariable("test", "value"); - container.setTransientVariable("test", "value"); - assertThat(container.hasVariable("test")).isFalse(); - } -} diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml index 40485eed7ef..6865d402f45 100644 --- a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testMessageStartEventExpressionResolvesAtDeployment.bpmn20.xml @@ -8,7 +8,7 @@ - + diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml index c71c0c7f7f4..e5c8094b86c 100644 --- a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testSignalStartEventExpressionResolvesAtDeployment.bpmn20.xml @@ -8,7 +8,7 @@ - + diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml index ac4e9f65d3b..39bec22764c 100644 --- a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml @@ -1,5 +1,6 @@ - PT1H + ${variableContainer.definitionKey == 'timerExpressionProcess' ? 'PT1H' : 'PT2H'}