Skip to content

Commit

Permalink
flowable#3884 Expression language support for conditional sequence fl…
Browse files Browse the repository at this point in the history
…ow and conditional events
  • Loading branch information
rasmusfaber committed May 14, 2024
1 parent 3f8058b commit 5da58be
Show file tree
Hide file tree
Showing 26 changed files with 357 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,10 @@ protected void writeConditionalDefinition(Event parentEvent, ConditionalEventDef

if (StringUtils.isNotEmpty(conditionalDefinition.getConditionExpression())) {
xtw.writeStartElement(ELEMENT_CONDITION);
if (conditionalDefinition.getConditionLanguage() != null) {
xtw.writeAttribute(XSI_PREFIX, XSI_NAMESPACE, "type", "tFormalExpression");
BpmnXMLUtil.writeDefaultAttribute(BpmnXMLConstants.ATTRIBUTE_SCRIPT_LANGUAGE, conditionalDefinition.getConditionLanguage(), xtw);
}
xtw.writeCharacters(conditionalDefinition.getConditionExpression());
xtw.writeEndElement();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import javax.xml.stream.XMLStreamWriter;

import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.converter.util.BpmnXMLUtil;
import org.flowable.bpmn.model.BaseElement;
import org.flowable.bpmn.model.BpmnModel;
Expand Down Expand Up @@ -67,6 +68,9 @@ protected void writeAdditionalChildElements(BaseElement element, BpmnModel model
if (StringUtils.isNotEmpty(sequenceFlow.getConditionExpression())) {
xtw.writeStartElement(ELEMENT_FLOW_CONDITION);
xtw.writeAttribute(XSI_PREFIX, XSI_NAMESPACE, "type", "tFormalExpression");
if (sequenceFlow.getConditionLanguage() != null) {
BpmnXMLUtil.writeDefaultAttribute(BpmnXMLConstants.ATTRIBUTE_SCRIPT_LANGUAGE, sequenceFlow.getConditionLanguage(), xtw);
}
xtw.writeCData(sequenceFlow.getConditionExpression());
xtw.writeEndElement();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ public String getElementName() {

@Override
public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, BpmnModel model) throws Exception {
if (!(parentElement instanceof SequenceFlow)) {
if (!(parentElement instanceof SequenceFlow sequenceFlow)) {
return;
}

((SequenceFlow) parentElement).setConditionExpression(xtr.getElementText().trim());
sequenceFlow.setConditionLanguage(xtr.getAttributeValue(null, ATTRIBUTE_SCRIPT_LANGUAGE));
sequenceFlow.setConditionExpression(xtr.getElementText().trim());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ public String getElementName() {

@Override
public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, BpmnModel model) throws Exception {
if (!(parentElement instanceof ConditionalEventDefinition)) {
if (!(parentElement instanceof ConditionalEventDefinition conditionalEventDefinition)) {
return;
}

((ConditionalEventDefinition) parentElement).setConditionExpression(xtr.getElementText().trim());
conditionalEventDefinition.setConditionLanguage(xtr.getAttributeValue(null, ATTRIBUTE_SCRIPT_LANGUAGE));
conditionalEventDefinition.setConditionExpression(xtr.getElementText().trim());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
public class ConditionalEventDefinition extends EventDefinition {

protected String conditionExpression;
protected String conditionLanguage;

public String getConditionExpression() {
return conditionExpression;
Expand All @@ -27,6 +28,14 @@ public void setConditionExpression(String conditionExpression) {
this.conditionExpression = conditionExpression;
}

public String getConditionLanguage() {
return conditionLanguage;
}

public void setConditionLanguage(String conditionLanguage) {
this.conditionLanguage = conditionLanguage;
}

@Override
public ConditionalEventDefinition clone() {
ConditionalEventDefinition clone = new ConditionalEventDefinition();
Expand All @@ -37,5 +46,6 @@ public ConditionalEventDefinition clone() {
public void setValues(ConditionalEventDefinition otherDefinition) {
super.setValues(otherDefinition);
setConditionExpression(otherDefinition.getConditionExpression());
setConditionLanguage(otherDefinition.getConditionLanguage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
public class SequenceFlow extends FlowElement {

protected String conditionExpression;
protected String conditionLanguage;
protected String sourceRef;
protected String targetRef;
protected String skipExpression;
Expand Down Expand Up @@ -60,6 +61,14 @@ public void setConditionExpression(String conditionExpression) {
this.conditionExpression = conditionExpression;
}

public String getConditionLanguage() {
return conditionLanguage;
}

public void setConditionLanguage(String conditionLanguage) {
this.conditionLanguage = conditionLanguage;
}

public String getSourceRef() {
return sourceRef;
}
Expand Down Expand Up @@ -123,6 +132,7 @@ public SequenceFlow clone() {
public void setValues(SequenceFlow otherFlow) {
super.setValues(otherFlow);
setConditionExpression(otherFlow.getConditionExpression());
setConditionLanguage(otherFlow.getConditionLanguage());
setSourceRef(otherFlow.getSourceRef());
setTargetRef(otherFlow.getTargetRef());
setSkipExpression(otherFlow.getSkipExpression());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.flowable.common.engine.impl.javax.el.MapELResolver;
import org.flowable.common.engine.impl.javax.el.ValueExpression;
import org.flowable.common.engine.impl.persistence.deploy.DeploymentCache;
import org.flowable.common.engine.impl.scripting.ScriptingEngines;

/**
* Default {@link ExpressionManager} implementation that contains the logic for creating
Expand All @@ -43,6 +44,7 @@
*/
public class DefaultExpressionManager implements ExpressionManager {

public static final String JUEL_EXPRESSION_LANGUAGE = "juel";
protected ExpressionFactory expressionFactory;
protected List<FlowableFunctionDelegate> functionDelegates;
protected FlowableFunctionResolver functionResolver;
Expand Down Expand Up @@ -70,44 +72,60 @@ public DefaultExpressionManager(Map<Object, Object> beans) {

@Override
public Expression createExpression(String text) {

if (isCacheEnabled(text)) {
Expression cachedExpression = expressionCache.get(text);
return createExpression(text, ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE);
}

@Override
public Expression createExpression(String text, String language) {
String expressionText = text.trim();

if (language == null || ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE.equals(language)) {
return createJuelExpression(expressionText);
} else {
return createScriptEngineExpression(expressionText, language);
}
}

protected boolean isCacheEnabled(String text) {
return expressionCache != null && (expressionTextLengthCacheLimit < 0 || text.length() <= expressionTextLengthCacheLimit);
}

protected Expression createJuelExpression(String expressionText) {
if (isCacheEnabled(expressionText)) {
Expression cachedExpression = expressionCache.get(expressionText);
if (cachedExpression != null) {
return cachedExpression;
}
}

if (parsingElContext == null) {
this.parsingElContext = new ParsingElContext(functionResolver);
} else if (parsingElContext.getFunctionMapper() != null && parsingElContext.getFunctionMapper() instanceof FlowableFunctionMapper) {
((FlowableFunctionMapper) parsingElContext.getFunctionMapper()).setFunctionResolver(functionResolver);
}

String expressionText = text.trim();

ValueExpression valueExpression = expressionFactory.createValueExpression(parsingElContext, expressionText, Object.class);
Expression expression = createJuelExpression(text, valueExpression);
if (isCacheEnabled(text)) {
expressionCache.add(text, expression);
Expression expression = createJuelExpression(expressionText, valueExpression);

if (isCacheEnabled(expressionText)) {
expressionCache.add(expressionText, expression);
}

return expression;
}

protected boolean isCacheEnabled(String text) {
return expressionCache != null && (expressionTextLengthCacheLimit < 0 || text.length() <= expressionTextLengthCacheLimit);
return expression;
}

protected Expression createJuelExpression(String expression, ValueExpression valueExpression) {
return new JuelExpression(this, valueExpression, expression);
}

private Expression createScriptEngineExpression(String text, String language) {
return new ScriptEngineExpression(text, language);
}

public void setExpressionFactory(ExpressionFactory expressionFactory) {
this.expressionFactory = expressionFactory;
}



@Override
public ELContext getElContext(VariableContainer variableContainer) {
ELResolver elResolver = getOrCreateStaticElResolver();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ public interface ExpressionManager {
* Expression are resolved against a {@link VariableContainer} (e.g. a process Execution, a case instance plan item, etc.)
*/
Expression createExpression(String expression);


/**
* Creates an {@link Expression} instance from the given String using the specified scripting language.
* Expression are resolved against a {@link VariableContainer} (e.g. a process Execution, a case instance plan item, etc.)
*/
Expression createExpression(String expression, String expressionLanguage);

/**
* Creates an {@link ELContext} against which {@link Expression} instance can be resolved.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* 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 org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.AbstractEngineConfiguration;
import org.flowable.common.engine.impl.ScriptingEngineAwareEngineConfiguration;
import org.flowable.common.engine.impl.context.Context;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.common.engine.impl.interceptor.EngineConfigurationConstants;
import org.flowable.common.engine.impl.javax.el.ELContext;
import org.flowable.common.engine.impl.javax.el.MethodNotFoundException;
import org.flowable.common.engine.impl.javax.el.PropertyNotFoundException;
import org.flowable.common.engine.impl.javax.el.ValueExpression;
import org.flowable.common.engine.impl.scripting.ScriptEngineRequest;
import org.flowable.common.engine.impl.scripting.ScriptingEngines;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;

/**
* Expression implementation backed by the {@link javax.script.ScriptEngineManager}.
*/
public class ScriptEngineExpression implements Expression {

private static final long serialVersionUID = -6712195382739315692L;

protected String expressionText;
protected String expressionLanguage;

public ScriptEngineExpression(String expressionText, String expressionLanguage) {
this.expressionText = expressionText;
this.expressionLanguage = expressionLanguage;
}

@Override
public Object getValue(VariableContainer variableContainer) {
ScriptEngineRequest.Builder builder = ScriptEngineRequest.builder()
.script(expressionText)
.language(expressionLanguage)
.variableContainer(variableContainer);
return getScriptingEngines().evaluate(builder.build()).getResult();
}

private ScriptingEngines getScriptingEngines() {
CommandContext commandContext = Context.getCommandContext();
if (commandContext == null) {
throw new FlowableException("No CommandContext");
}
AbstractEngineConfiguration engineConfiguration = commandContext.getEngineConfigurations().get(EngineConfigurationConstants.KEY_PROCESS_ENGINE_CONFIG);
if (!(engineConfiguration instanceof ScriptingEngineAwareEngineConfiguration scriptingEngineAware)) {
throw new FlowableException("Process engine configuration is not scripting aware");
}
return scriptingEngineAware.getScriptingEngines();
}

@Override
public void setValue(Object value, VariableContainer variableContainer) {
throw new FlowableException("Cannot change value with " + expressionLanguage);
}

@Override
public String toString() {
return expressionText;
}

@Override
public String getExpressionText() {
return expressionText;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ public interface FlowableConditionalEvent extends FlowableActivityEvent {
*/
String getConditionExpression();

/**
* @return the scripting language of the condition expression.
*/
String getConditionLanguage();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
public class FlowableConditionalEventImpl extends FlowableActivityEventImpl implements FlowableConditionalEvent {

protected String conditionExpression;
protected String conditionLanguage;

public FlowableConditionalEventImpl(FlowableEngineEventType type) {
super(type);
Expand All @@ -34,4 +35,12 @@ public String getConditionExpression() {
public void setConditionExpression(String conditionExpression) {
this.conditionExpression = conditionExpression;
}

public String getConditionLanguage() {
return conditionLanguage;
}

public void setConditionLanguage(String conditionLanguage) {
this.conditionLanguage = conditionLanguage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ public static FlowableMessageEvent createMessageEvent(FlowableEngineEventType ty
return newEvent;
}

public static FlowableConditionalEvent createConditionalEvent(FlowableEngineEventType type, String activityId, String conditionExpression,
public static FlowableConditionalEvent createConditionalEvent(FlowableEngineEventType type, String activityId, String conditionExpression, String conditionLanguage,
String executionId, String processInstanceId, String processDefinitionId) {

FlowableConditionalEventImpl newEvent = new FlowableConditionalEventImpl(type);
Expand All @@ -377,6 +377,7 @@ public static FlowableConditionalEvent createConditionalEvent(FlowableEngineEven
newEvent.setProcessDefinitionId(processDefinitionId);
newEvent.setProcessInstanceId(processInstanceId);
newEvent.setConditionExpression(conditionExpression);
newEvent.setConditionLanguage(conditionLanguage);
return newEvent;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ protected void evaluateEventSubProcesses(List<EventSubProcess> eventSubProcesses
boolean conditionIsTrue = false;
String conditionExpression = conditionalEventDefinition.getConditionExpression();
if (StringUtils.isNotEmpty(conditionExpression)) {
Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(conditionExpression);
String conditionLanguage = conditionalEventDefinition.getConditionLanguage();
Expression expression = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager().createExpression(conditionExpression, conditionLanguage);
Object result = expression.getValue(parentExecution);
if (result instanceof Boolean && (Boolean) result) {
conditionIsTrue = true;
Expand Down
Loading

0 comments on commit 5da58be

Please sign in to comment.