Gateway Expressions do not support delegate executions

While working on Scripting based Delegates with SpringBoot, Camunda-Run, and Source Code / Bean Runtime Refresh! - #7 by tasso94

I get differing behaviour when accessing a bean from a service task ${myBean} vs on a gateway: ${myLogicBean.result(execution)}

relevant error:

aused by: org.camunda.bpm.engine.ProcessEngineException: Unknown property used in expression: ${myDecision1.result}. Cause: Could not find property result in class com.sun.proxy.$Proxy88
	at org.camunda.bpm.engine.impl.el.JuelExpression.getValue(JuelExpression.java:63)
	at org.camunda.bpm.engine.impl.el.UelExpressionCondition.evaluate(UelExpressionCondition.java:50)
	at org.camunda.bpm.engine.impl.el.UelExpressionCondition.evaluate(UelExpressionCondition.java:45)
	at org.camunda.bpm.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.doLeave(ExclusiveGatewayActivityBehavior.java:64)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityLeave.execute(PvmAtomicOperationActivityLeave.java:56)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityLeave.execute(PvmAtomicOperationActivityLeave.java:32)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl$6.callback(PvmExecutionImpl.java:1975)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl$6.callback(PvmExecutionImpl.java:1972)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.continueExecutionIfNotCanceled(PvmExecutionImpl.java:2042)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.dispatchDelayedEventsAndPerformOperation(PvmExecutionImpl.java:1991)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.dispatchDelayedEventsAndPerformOperation(PvmExecutionImpl.java:1972)
	at org.camunda.bpm.engine.impl.bpmn.behavior.FlowNodeActivityBehavior.leave(FlowNodeActivityBehavior.java:52)
	at org.camunda.bpm.engine.impl.bpmn.behavior.FlowNodeActivityBehavior.execute(FlowNodeActivityBehavior.java:44)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute$2.callback(PvmAtomicOperationActivityExecute.java:61)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute$2.callback(PvmAtomicOperationActivityExecute.java:50)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.continueIfExecutionDoesNotAffectNextOperation(PvmExecutionImpl.java:2036)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute.execute(PvmAtomicOperationActivityExecute.java:42)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute.execute(PvmAtomicOperationActivityExecute.java:31)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl$6.callback(PvmExecutionImpl.java:1975)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl$6.callback(PvmExecutionImpl.java:1972)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.continueExecutionIfNotCanceled(PvmExecutionImpl.java:2042)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.dispatchDelayedEventsAndPerformOperation(PvmExecutionImpl.java:1991)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.dispatchDelayedEventsAndPerformOperation(PvmExecutionImpl.java:1972)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationTransitionNotifyListenerStart.eventNotificationsCompleted(PvmAtomicOperationTransitionNotifyListenerStart.java:60)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationTransitionNotifyListenerStart.eventNotificationsCompleted(PvmAtomicOperationTransitionNotifyListenerStart.java:30)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:66)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationTransitionCreateScope.scopeCreated(PvmAtomicOperationTransitionCreateScope.java:38)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationCreateScope.execute(PvmAtomicOperationCreateScope.java:54)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationCreateScope.execute(PvmAtomicOperationCreateScope.java:28)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:118)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext$1.call(CommandInvocationContext.java:102)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext$1.call(CommandInvocationContext.java:100)
	at org.camunda.bpm.engine.impl.context.ProcessApplicationClassloaderInterceptor.call(ProcessApplicationClassloaderInterceptor.java:48)
	at org.camunda.bpm.application.AbstractProcessApplication.execute(AbstractProcessApplication.java:121)
	at org.camunda.bpm.application.AbstractProcessApplication.execute(AbstractProcessApplication.java:132)
	at org.camunda.bpm.engine.impl.context.Context.executeWithinProcessApplication(Context.java:206)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:100)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.start(PvmExecutionImpl.java:287)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.start(ExecutionEntity.java:458)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.startWithFormProperties(PvmExecutionImpl.java:267)
	at org.camunda.bpm.engine.impl.cmd.SubmitStartFormCmd.execute(SubmitStartFormCmd.java:73)
	at org.camunda.bpm.engine.impl.cmd.SubmitStartFormCmd.execute(SubmitStartFormCmd.java:41)
	at org.camunda.bpm.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:28)
	at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:110)
	at org.camunda.bpm.engine.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:46)
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
	at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:44)
	at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:70)
	at org.camunda.bpm.engine.impl.interceptor.CommandCounterInterceptor.execute(CommandCounterInterceptor.java:35)
	at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:33)
	at org.camunda.bpm.engine.impl.FormServiceImpl.submitStartForm(FormServiceImpl.java:79)
	at org.camunda.bpm.engine.rest.sub.repository.impl.ProcessDefinitionResourceImpl.submitForm(ProcessDefinitionResourceImpl.java:188)
	... 81 more
Caused by: org.camunda.bpm.engine.impl.javax.el.PropertyNotFoundException: Could not find property result in class com.sun.proxy.$Proxy88
	at org.camunda.bpm.engine.impl.javax.el.BeanELResolver.toBeanProperty(BeanELResolver.java:621)
	at org.camunda.bpm.engine.impl.javax.el.BeanELResolver.getValue(BeanELResolver.java:298)
	at org.camunda.bpm.engine.impl.javax.el.CompositeELResolver.getValue(CompositeELResolver.java:231)
	at org.camunda.bpm.engine.impl.juel.AstProperty.eval(AstProperty.java:61)
	at org.camunda.bpm.engine.impl.juel.AstEval.eval(AstEval.java:50)
	at org.camunda.bpm.engine.impl.juel.AstNode.getValue(AstNode.java:26)
	at org.camunda.bpm.engine.impl.juel.TreeValueExpression.getValue(TreeValueExpression.java:114)
	at org.camunda.bpm.engine.impl.delegate.ExpressionGetInvocation.invoke(ExpressionGetInvocation.java:40)
	at org.camunda.bpm.engine.impl.delegate.DelegateInvocation.proceed(DelegateInvocation.java:58)
	at org.camunda.bpm.engine.impl.delegate.DefaultDelegateInterceptor.handleInvocationInContext(DefaultDelegateInterceptor.java:92)
	at org.camunda.bpm.engine.impl.delegate.DefaultDelegateInterceptor.handleInvocation(DefaultDelegateInterceptor.java:63)
	at org.camunda.bpm.engine.impl.el.JuelExpression.getValue(JuelExpression.java:60)
	... 179 more

From debug is seems that a expression for a JavaDelegate in a activity like a service task is iterated over twice: once as a expression and second as the actual class. But with gateways, the expression does not get rendered twice and instead fails as a proxy. Inspecting the data debugger you can see the expression was able to get the expected bean, but the JUEL was unable to process it further.

I have tried with basic beans ${myBean}, methods ${myBean.eval(…), and with properties ${myBean.myProp}}.

Any ideas what would cause this?

possible relation:

Possible reactions:

Example:

vs

ya so the issue appears to be that the Expressions that are used on gateways go through the UelExpressionCondition.class and are evaluated without being parsed by a DelegateInterceptor.

@thorben any thoughts on a PR to make a change so the Gateway’s pass their expressions through a delegate interceptor?

@thorben okay i have created and tested the following based on how JavaDelegates are processed:

import org.camunda.bpm.engine.delegate.DelegateExecution

interface ConditionDelegate {

    fun evaluate(execution: DelegateExecution): Boolean

}
import org.camunda.bpm.engine.delegate.DelegateExecution
import org.camunda.bpm.engine.impl.delegate.DelegateInvocation


open class ConditionDelegateInvocation(
    protected val delegateInstance: ConditionDelegate,
    protected val execution: DelegateExecution
) : DelegateInvocation(execution, null) {

    @Throws(java.lang.Exception::class)
    override fun invoke() {
        invocationResult = delegateInstance.evaluate(execution)
    }
}
package org.camunda.bpm.engine.impl.el;

import org.camunda.bpm.engine.ProcessEngineException;
import static org.camunda.bpm.engine.impl.util.EnsureUtil.ensureInstanceOf;
import static org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull;

import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.VariableScope;
import org.camunda.bpm.engine.impl.Condition;
import org.camunda.bpm.engine.impl.context.Context;
import org.camunda.bpm.engine.impl.javax.el.PropertyNotFoundException;
import org.camunda.bpm.run.plugin.springbeans.ConditionDelegate;
import org.camunda.bpm.run.plugin.springbeans.ConditionDelegateInvocation;


public class UelExpressionCondition implements Condition {

    protected Expression expression;

    public UelExpressionCondition(Expression expression) {
        this.expression = expression;
    }

    @Override
    public boolean evaluate(DelegateExecution execution) {
        return evaluate(execution, execution);
    }

    @Override
    public boolean evaluate(VariableScope scope, DelegateExecution execution) {
        Object result = expression.getValue(scope, execution);

        //NEW SECTION:
        if (result instanceof ConditionDelegate){
            ConditionDelegateInvocation invocation = new ConditionDelegateInvocation((ConditionDelegate)result, execution);

            try {
                Context.getProcessEngineConfiguration()
                        .getDelegateInterceptor()
                        .handleInvocation(invocation);
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new ProcessEngineException(e);
            }

            result = invocation.getInvocationResult();
        }
        //END OF NEW SECTION

        ensureNotNull("condition expression returns null", "result", result);
        ensureInstanceOf("condition expression returns non-Boolean", "result", result, Boolean.class);
        return (Boolean) result;
    }

    @Override
    public boolean tryEvaluate(VariableScope scope, DelegateExecution execution) {
        boolean result = false;
        try {
            result = evaluate(scope, execution);
        } catch (ProcessEngineException pee) {
            if (!(pee.getCause() instanceof PropertyNotFoundException)) {
                throw pee;
            }
        }
        return result;
    }
}

If we can add the following then Gateway Expressions can support delegates.

@thorben @tasso94 thoughts on adding the ConditionDelegate? Alternative ideas on where to place this method capability?

would then add the logic to the other gateway behaviours

ticket for tracking : https://jira.camunda.com/browse/CAM-13335?filter=-2

Pull Request created:

1 Like

Hi @StephenOTT,

Thank you for analyzing this.

Could you please provide a failing test case based on our JUnit template? This would allow us to debug the problem.

Best,
Tassilo

Hey Stephen,

It’s great that you are contributing and I think I generally understand the idea, but it’s not entirely clear yet. As Tassilo said a test with the failure from Gateway Expressions do not support delegate executions would really help us validate if your proposed solution is the best one, especially comparing the two variants (gateway vs service task).

Thanks!

Cheers,
Thorben

A gateway can use scripts. But a gateway cannot use a delegate/Java code. The goal here is to enable “Java code” to be used as logic code (instead of baking the logic into the expression)

@thorben @tasso94
Unit Test: GitHub - StephenOTT/expression-delegates

Expectation is that using the “Expression.class” delegate would be supported on any expression.