diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/DefinitionVariableContainer.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/DefinitionVariableContainer.java new file mode 100644 index 00000000000..56de1aa73be --- /dev/null +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/DefinitionVariableContainer.java @@ -0,0 +1,87 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.common.engine.impl.el; + +import java.util.Set; + +import org.flowable.common.engine.api.variable.VariableContainer; + +/** + * 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. + * + * @author Christopher Welsch + */ +public class DefinitionVariableContainer implements VariableContainer { + + protected String definitionId; + protected String definitionKey; + protected String deploymentId; + protected String scopeType; + protected 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; + } + + public String getDefinitionId() { + return definitionId; + } + + public String getDefinitionKey() { + return definitionKey; + } + + 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..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,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.impl.el.DefinitionVariableContainer; /** * Manages event subscriptions for newly-deployed process definitions and their previous versions. @@ -173,7 +174,7 @@ protected void insertSignalEvent(SignalEventDefinition signalEventDefinition, St EventSubscriptionService eventSubscriptionService = processEngineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService(); SignalEventSubscriptionEntity subscriptionEntity = eventSubscriptionService.createSignalEventSubscription(); - String signalName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel,null); + String signalName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, processDefinition); subscriptionEntity.setEventName(signalName); subscriptionEntity.setActivityId(startEvent.getId()); @@ -191,8 +192,9 @@ protected void insertMessageEvent(MessageEventDefinition messageEventDefinition, ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); EventSubscriptionService eventSubscriptionService = processEngineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService(); + // look for subscriptions for the same name in db: - String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, null); + 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 e3966f87a64..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 @@ -73,18 +73,12 @@ protected List getTimerDeclarations(ProcessDefinitionEntity proc if (CollectionUtil.isNotEmpty(startEvent.getEventDefinitions())) { EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0); if (eventDefinition instanceof TimerEventDefinition timerEventDefinition) { + TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, startEvent, - false, null, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), + false, processDefinition, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); - if (timerJob != null) { - timerJob.setProcessDefinitionId(processDefinition.getId()); - - if (processDefinition.getTenantId() != null) { - timerJob.setTenantId(processDefinition.getTenantId()); - } - timers.add(timerJob); - } + 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..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,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.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; @@ -1585,7 +1586,10 @@ 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(), + messageExecution.getProcessDefinitionKey(), 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 +1622,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(), + signalExecution.getProcessDefinitionKey(), 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..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,9 +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.engine.repository.ProcessDefinition; import org.flowable.variable.service.impl.el.NoExecutionVariableScope; /** @@ -34,7 +37,23 @@ 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) { + public static String determineSignalName(CommandContext commandContext, SignalEventDefinition signalEventDefinition, + BpmnModel bpmnModel, ProcessDefinition processDefinition) { + + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), + processDefinition.getKey(), processDefinition.getDeploymentId(), ScopeTypes.BPMN, processDefinition.getTenantId()); + + return determineSignalName(commandContext, signalEventDefinition, bpmnModel, definitionVariableContainer); + } + + /** + * 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 variableContainer) { String signalName = null; if (StringUtils.isNotEmpty(signalEventDefinition.getSignalRef())) { Signal signal = bpmnModel.getSignal(signalEventDefinition.getSignalRef()); @@ -51,12 +70,28 @@ public static String determineSignalName(CommandContext commandContext, SignalEv if (StringUtils.isNotEmpty(signalName)) { Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(signalName); - return expression.getValue(execution != null ? execution : NoExecutionVariableScope.getSharedInstance()).toString(); + return expression.getValue(variableContainer != null ? variableContainer : NoExecutionVariableScope.getSharedInstance()).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 + * - 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, + ProcessDefinition processDefinition) { + + DefinitionVariableContainer definitionVariableContainer = new DefinitionVariableContainer(processDefinition.getId(), + processDefinition.getKey(), processDefinition.getDeploymentId(), ScopeTypes.BPMN, processDefinition.getTenantId()); + + return determineMessageName(commandContext, messageEventDefinition, definitionVariableContainer); + } /** * Determines the event name of the {@link org.flowable.bpmn.model.MessageEventDefinition} that is passed: * - if a message ref is set, it has precedence @@ -65,7 +100,8 @@ public static String determineSignalName(CommandContext commandContext, SignalEv * 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) { + public static String determineMessageName(CommandContext commandContext, MessageEventDefinition messageEventDefinition, + VariableContainer variableContainer) { String messageName = null; if (StringUtils.isNotEmpty(messageEventDefinition.getMessageRef())) { return messageEventDefinition.getMessageRef(); @@ -77,10 +113,9 @@ public static String determineMessageName(CommandContext commandContext, Message if (StringUtils.isNotEmpty(messageName)) { Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(messageName); - return expression.getValue(execution != null ? execution : NoExecutionVariableScope.getSharedInstance()).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 b87b9393dad..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; @@ -154,23 +156,12 @@ protected void restorePreviousStartEventsIfNeeded(ProcessDefinition processDefin protected void restoreTimerStartEvent(ProcessDefinition previousProcessDefinition, StartEvent startEvent, EventDefinition eventDefinition) { TimerEventDefinition timerEventDefinition = (TimerEventDefinition) eventDefinition; - TimerJobEntity timer = TimerUtil.createTimerEntityForTimerEventDefinition((TimerEventDefinition) eventDefinition, startEvent, - false, null, 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(), - timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); - - timerJob.setProcessDefinitionId(previousProcessDefinition.getId()); - if (previousProcessDefinition.getTenantId() != null) { - timerJob.setTenantId(previousProcessDefinition.getTenantId()); - } + TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, startEvent, + false, previousProcessDefinition, TimerStartEventJobHandler.TYPE, TimerEventHandler.createConfiguration(startEvent.getId(), + timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); - engineConfiguration.getJobServiceConfiguration().getTimerJobService().scheduleTimerJob(timerJob); - } + engineConfiguration.getJobServiceConfiguration().getTimerJobService().scheduleTimerJob(timerJob); } protected void restoreSignalStartEvent(ProcessDefinition previousProcessDefinition, BpmnModel bpmnModel, StartEvent startEvent, EventDefinition eventDefinition) { @@ -178,7 +169,7 @@ protected void restoreSignalStartEvent(ProcessDefinition previousProcessDefiniti SignalEventDefinition signalEventDefinition = (SignalEventDefinition) eventDefinition; SignalEventSubscriptionEntity subscriptionEntity = engineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService().createSignalEventSubscription(); - String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, null); + String eventName = EventDefinitionExpressionUtil.determineSignalName(commandContext, signalEventDefinition, bpmnModel, previousProcessDefinition); subscriptionEntity.setEventName(eventName); subscriptionEntity.setActivityId(startEvent.getId()); subscriptionEntity.setProcessDefinitionId(previousProcessDefinition.getId()); @@ -199,7 +190,7 @@ protected void restoreMessageStartEvent(ProcessDefinition previousProcessDefinit CommandContext commandContext = Context.getCommandContext(); MessageEventSubscriptionEntity newSubscription = engineConfiguration.getEventSubscriptionServiceConfiguration().getEventSubscriptionService().createMessageEventSubscription(); - String messageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, null); + 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 158dc1c592c..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,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.impl.el.DefinitionVariableContainer; import tools.jackson.databind.node.ObjectNode; @@ -156,8 +157,7 @@ public ProcessInstance createAndStartProcessInstanceByMessage(ProcessDefinition if (flowElement instanceof StartEvent startEvent) { if (CollectionUtil.isNotEmpty(startEvent.getEventDefinitions()) && startEvent.getEventDefinitions() .get(0) instanceof MessageEventDefinition messageEventDefinition) { - - String actualMessageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, null); + String actualMessageName = EventDefinitionExpressionUtil.determineMessageName(commandContext, messageEventDefinition, processDefinition); if (Objects.equals(actualMessageName, messageName)) { initialFlowElement = flowElement; break; @@ -461,7 +461,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.getProcessDefinitionKey(), 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..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 @@ -30,8 +30,11 @@ import org.flowable.bpmn.model.TimerEventDefinition; import org.flowable.common.engine.api.FlowableException; 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; import org.flowable.common.engine.impl.calendar.BusinessCalendar; import org.flowable.common.engine.impl.calendar.CycleBusinessCalendar; import org.flowable.common.engine.impl.calendar.DueDateBusinessCalendar; @@ -41,13 +44,13 @@ 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; 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,26 +61,72 @@ public class TimerUtil { /** * The event definition on which the timer is based. - * + *

+ * 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, 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) { + timer.setProcessDefinitionId(processDefinition.getId()); + + if (processDefinition.getTenantId() != null) { + timer.setTenantId(processDefinition.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()); + } + } + + 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; 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 - VariableScope scopeForExpression = executionEntity; - if (scopeForExpression == null) { - scopeForExpression = NoExecutionVariableScope.getSharedInstance(); - } - if (StringUtils.isNotEmpty(timerEventDefinition.getTimeDate())) { businessCalendarRef = DueDateBusinessCalendar.NAME; @@ -97,11 +146,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 +159,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 +169,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 +186,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 +196,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 +205,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 +225,31 @@ 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()); - } + // ACT-1951: intermediate catching timer events shouldn't repeat according to spec + if (currentFlowElement instanceof IntermediateCatchEvent) { + timer.setRepeat(null); } 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 null; + } + public static TimerJobEntity rescheduleTimerJob(String timerJobId, TimerEventDefinition timerEventDefinition) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); TimerJobService timerJobService = processEngineConfiguration.getJobServiceConfiguration().getTimerJobService(); @@ -241,7 +263,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 +272,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..338175537f7 --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.java @@ -0,0 +1,142 @@ +/* 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.impl.el.DefinitionVariableContainer}. + * + * @author Christopher Welsch + */ +public class StartEventExpressionWithDefinitionContainerTest extends PluggableFlowableTestCase { + + @Test + @Deployment + public void testSignalStartEventExpressionResolvesAtDeployment() { + // 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("signalExpressionProcessSignal"); + + // Verify that sending the signal creates a process instance + runtimeService.signalEventReceived("signalExpressionProcessSignal"); + List instances = runtimeService.createProcessInstanceQuery() + .processDefinitionKey("signalExpressionProcess") + .list(); + assertThat(instances).hasSize(1); + } + + @Test + @Deployment + public void testMessageStartEventExpressionResolvesAtDeployment() { + // 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("messageExpressionProcessMessage"); + + // Verify that sending the message creates a process instance + ProcessInstance instance = runtimeService.startProcessInstanceByMessage("messageExpressionProcessMessage"); + assertThat(instance).isNotNull(); + assertThat(instance.getProcessDefinitionKey()).isEqualTo("messageExpressionProcess"); + } + + @Test + @Deployment + public void testTimerStartEventExpressionResolvesAtDeployment() { + // 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() { + List subscriptions = runtimeService.createEventSubscriptionQuery() + .eventType("signal") + .tenantId("tenantA") + .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() { + List subscriptions = runtimeService.createEventSubscriptionQuery() + .eventType("message") + .tenantId("tenantA") + .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() { + 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/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..6865d402f45 --- /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..e5c8094b86c --- /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..39bec22764c --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/StartEventExpressionWithDefinitionContainerTest.testTimerStartEventExpressionResolvesAtDeployment.bpmn20.xml @@ -0,0 +1,42 @@ + + + + + + + ${variableContainer.definitionKey == 'timerExpressionProcess' ? 'PT1H' : 'PT2H'} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +