Скрипты
|
Этот раздел перенесён из документации Camunda 7 и в дальнейшем будет доработан с учётом особенностей OpenBPM Engine |
OpenBPM Engine поддерживает скриптинг с реализациями script engine, совместимыми с JSR-223. Сейчас мы тестируем интеграцию для Groovy, JavaScript, JRuby и Jython. Чтобы использовать script engine, необходимо добавить соответствующий jar в classpath.
|
В предсобранные дистрибутивы OpenBPM Engine мы включаем GraalVM JavaScript. Дополнительную информацию см. в разделе JavaScript Considerations #_javascript_considerations. В предсобранные дистрибутивы OpenBPM Engine мы также включаем Groovy. |
В следующей таблице приведен обзор BPMN-элементов, поддерживающих выполнение скриптов.
| BPMN-элемент | Поддержка скриптов |
|---|---|
Скрипт внутри script task |
|
"Processes, Activities, Sequence Flows, Gateways and Events" |
Скрипт как execution listener |
Скрипт как task listener |
|
Скрипт как condition expression в sequence flow |
|
"All Tasks, All Events, Transactions, Subprocesses and Connectors" |
Скрипт внутри маппинга параметров inputOutput |
Использование Script Task
С помощью BPMN 2.0 script task вы можете добавить скрипт в свой BPM-процесс (подробнее см. в справочнике BPMN 2.0).
Следующий процесс - простой пример с Groovy script task, который суммирует элементы массива.
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
targetNamespace="http://camunda.org/example">
<process id="process" isExecutable="true">
<startEvent id="start"/>
<sequenceFlow id="sequenceFlow1" sourceRef="start" targetRef="task"/>
<scriptTask id="task" name="Groovy Script" scriptFormat="groovy">
<script>
<![CDATA[
sum = 0
for ( i in inputArray ) {
sum += i
}
println "Sum: " + sum
]]>
</script>
</scriptTask>
<sequenceFlow id="sequenceFlow2" sourceRef="task" targetRef="end"/>
<endEvent id="end"/>
</process>
</definitions>
Для запуска процесса необходима переменная inputArray.
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("inputArray", new Integer[]{5, 23, 42});
runtimeService.startProcessInstanceByKey("process", variables);
Использование скриптов как Execution Listener
Помимо Java-кода и expression language, OpenBPM Engine также поддерживает выполнение скрипта как execution listener. Общую информацию об execution listener см. в соответствующем разделе.
Чтобы использовать скрипт как execution listener, нужно добавить элемент camunda:script как дочерний
элемент camunda:executionListener. Во время вычисления скрипта доступна переменная execution,
соответствующая интерфейсу DelegateExecution.
Следующий пример показывает использование скриптов как execution listener.
<process id="process" isExecutable="true">
<extensionElements>
<camunda:executionListener event="start">
<camunda:script scriptFormat="groovy">
println "Process " + execution.eventName + "ed"
</camunda:script>
</camunda:executionListener>
</extensionElements>
<startEvent id="start">
<extensionElements>
<camunda:executionListener event="end">
<camunda:script scriptFormat="groovy">
println execution.activityId + " " + execution.eventName + "ed"
</camunda:script>
</camunda:executionListener>
</extensionElements>
</startEvent>
<sequenceFlow id="flow1" startRef="start" targetRef="task">
<extensionElements>
<camunda:executionListener>
<camunda:script scriptFormat="groovy" resource="org/camunda/bpm/transition.groovy" />
</camunda:executionListener>
</extensionElements>
</sequenceFlow>
<!--
... remaining process omitted
-->
</process>
Использование скриптов как Task Listener
Аналогично execution listener, task listener также можно реализовать скриптами. Общая информация о task listener приведена в соответствующем разделе.
Чтобы использовать скрипт как task listener, нужно добавить элемент camunda:script как дочерний элемент
camunda:taskListener. Внутри скрипта доступна переменная task, соответствующая
интерфейсу DelegateTask.
Следующий пример показывает использование скриптов как task listener.
<userTask id="userTask">
<extensionElements>
<camunda:taskListener event="create">
<camunda:script scriptFormat="groovy">println task.eventName</camunda:script>
</camunda:taskListener>
<camunda:taskListener event="assignment">
<camunda:script scriptFormat="groovy" resource="org/camunda/bpm/assignemnt.groovy" />
</camunda:taskListener>
</extensionElements>
</userTask>
Использование скриптов как условий
Как альтернатива expression language, OpenBPM Engine позволяет использовать скрипты в
conditionExpression условных sequence flow. Для этого атрибут language элемента
conditionExpression нужно установить в нужный язык скриптов. Исходный код скрипта
задается текстовым содержимым элемента, как и для expression language. Другой способ задать
исходный код скрипта - определить внешний источник, как описано в разделе script source link:{{< relref "#script-source" >}}[].
Следующий пример показывает использование скриптов как условий. Переменная Groovy status - это
переменная процесса, доступная внутри скрипта.
<sequenceFlow>
<conditionExpression xsi:type="tFormalExpression" language="groovy">
status == 'closed'
</conditionExpression>
</sequenceFlow>
<sequenceFlow>
<conditionExpression xsi:type="tFormalExpression" language="groovy"
camunda:resource="org/camunda/bpm/condition.groovy" />
</sequenceFlow>
Использование скриптов как параметров inputOutput
С помощью элемента расширения OpenBPM Engine inputOutput можно маппить inputParameter или outputParameter
скриптом. Следующий пример процесса использует Groovy-скрипт из предыдущего примера, чтобы присвоить
переменной процесса x значение переменной Groovy sum для Java delegate.
|
Обратите внимание, что возвращается последнее выражение скрипта. Это применимо к Groovy,
JavaScript и JRuby, но не к Jython. Если вы хотите использовать Jython, ваш скрипт должен быть
одним выражением вроде |
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:camunda="http://activiti.org/bpmn"
targetNamespace="http://camunda.org/example">
<process id="process" isExecutable="true">
<startEvent id="start"/>
<sequenceFlow id="sequenceFlow1" sourceRef="start" targetRef="task"/>
<serviceTask id="task" camunda:class="io.openbpm.bpm.example.SumDelegate">
<extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="x">
<camunda:script scriptFormat="groovy">
<![CDATA[
sum = 0
for ( i in inputArray ) {
sum += i
}
sum
]]>
</camunda:script>
</camunda:inputParameter>
</camunda:inputOutput>
</extensionElements>
</serviceTask>
<sequenceFlow id="sequenceFlow2" sourceRef="task" targetRef="end"/>
<endEvent id="end"/>
</process>
</definitions>
После того как скрипт присвоит значение переменной sum, x можно использовать внутри кода
Java delegate.
public class SumDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
Integer x = (Integer) execution.getVariable("x");
// do something
}
}
Исходный код скрипта также можно загружать из внешнего ресурса так же, как описано для script task link:{{< relref "#script-source" >}}[].
<camunda:inputOutput>
<camunda:inputParameter name="x">
<camunda:script scriptFormat="groovy" resource="org/camunda/bpm/example/sum.groovy"/>
</camunda:inputParameter>
</camunda:inputOutput>
Кеширование Script Engine
Каждый раз, когда process engine доходит до точки, где нужно выполнить скрипт, process engine ищет Script Engine по имени языка. Поведение по умолчанию: если это первый запрос, создается новый Script Engine. Если Script Engine объявляет себя потокобезопасным, он также кешируется. Кеширование предотвращает создание process engine нового Script Engine при каждом запросе для одного и того же языка скрипта.
По умолчанию кеширование Script Engine происходит на уровне process application. Каждое process application хранит собственный экземпляр Script Engine для заданного языка. Это поведение можно отключить, установив флаг конфигурации process engine enableFetchScriptEngineFromProcessApplication в false. В таком случае Script Engine кешируются глобально на уровне process engine и разделяются между всеми process application. Подробнее о флаге конфигурации enableFetchScriptEngineFromProcessApplication см. в разделе ссылки на классы, предоставляемые process application.
Если вообще не требуется кешировать Script Engine, это можно отключить, установив флаг конфигурации process engine enableScriptEngineCaching в false.
Компиляция скриптов
Большинство script engine компилируют исходный код скрипта либо в Java-класс, либо в другой
промежуточный формат перед выполнением скрипта. Script engine, реализующие Java-интерфейс Compilable,
позволяют программам получать и кешировать результат компиляции скрипта. По умолчанию process engine
проверяет, поддерживает ли Script Engine компиляцию. Если поддерживает и кеширование Script Engine включено,
script engine компилирует скрипт и затем кеширует результат компиляции. Это предотвращает повторную
компиляцию исходного кода каждый раз при выполнении той же script task.
По умолчанию компиляция скриптов включена. Если нужно отключить компиляцию скриптов, установите флаг конфигурации process engine enableScriptCompilation в false.
Загрузка Script Engine
Если флаг конфигурации process engine enableFetchScriptEngineFromProcessApplication установлен в true, Script Engine также можно загружать из classpath process application. Для этого Script Engine можно упаковать как библиотеку внутри process application. Также можно установить Script Engine глобально.
Если модуль Script Engine должен быть установлен глобально и используется Wildfly, необходимо добавить зависимость модуля на Script Engine. Это можно сделать, добавив jboss-deployment-structure.xml в process application, например:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="org.apache.groovy.groovy-all"
services="import" />
</dependencies>
</deployment>
</jboss-deployment-structure>
Конфигурирование Script Engine
Большинство script engine предоставляют настройки для изменения семантики выполнения скриптов. Перед выполнением кода мы применяем следующие конфигурации по умолчанию для поддерживаемых script engine:
| Script Engine | Конфигурация по умолчанию |
|---|---|
GraalVM JavaScript |
Сконфигурирован так, чтобы разрешить host access и host class lookup: параметры |
Groovy |
Сконфигурирован на хранение только weak-ссылок на Java-методы: параметр |
Помимо этих опций по умолчанию, вы можете конфигурировать script engine любым из следующих способов:
-
Установить script engine-специфичные флаги конфигурации в process engine configuration.
-
Передать script engine-специфичные system properties.
-
Предоставить собственную реализацию интерфейса
ScriptEngineResolver.
Обратите внимание: для выполнения JavaScript в зависимости от вашего окружения вы можете выбирать script engine. Дополнительную информацию см. в разделе JavaScript Considerations #javascript-considerations.
Флаги process engine
Вы можете использовать следующие флаги конфигурации process engine, чтобы влиять на конфигурацию конкретных script engine:
-
configureScriptEngineHostAccess - определяет, доступны ли ресурсы host-языка, такие как классы и их методы.
-
enableScriptEngineLoadExternalResources - определяет, можно ли загружать внешние ресурсы из файловой системы.
-
enableScriptEngineNashornCompatibility - определяет, включен ли режим совместимости с Nashorn.
System properties
В зависимости от script engine, для влияния на его настройку можно использовать специфичные system properties. См. руководства разработчика для нужного script engine, чтобы узнать доступные параметры. Учтите, что поддерживаемые опции могут отличаться между версиями script engine.
System properties можно задать программно через System.setProperty(parameter, value) или как JVM-аргументы,
например, при запуске приложения из командной строки через -Dparameter=value. Большинство application server, таких как Wildfly и Tomcat, поддерживают передачу JVM-аргументов через переменные окружения JAVA_OPTS или JAVA_OPTIONS.
См. документацию вашего application server, чтобы узнать, как передавать JVM-аргументы. OpenBPM Engine Run также поддерживает установку
JVM-аргументов через переменную окружения JAVA_OPTS.
Пользовательский ScriptEngineResolver
Вы можете предоставить собственную реализацию ScriptEngineResolver для конфигурирования script engine. В зависимости от конкретного script engine,
этот подход может дать больше вариантов настройки. Добавить свой resolver в конфигурацию движка можно
методом #setScriptEngineResolver(ScriptEngineResolver).
Вы можете унаследоваться от io.openbpm.bpm.engine.impl.scripting.engine.DefaultScriptEngineResolver, если вам достаточно конфигурирования существующего
экземпляра script engine. Переопределив метод #configureScriptEngines(String, ScriptEngine) у DefaultScriptEngineResolver,
вы можете изменить настройки у экземпляра script engine, переданного в этот метод, как показано ниже:
public class CustomScriptEngineResolver extends DefaultScriptEngineResolver {
public CustomScriptEngineResolver(ScriptEngineManager scriptEngineManager) {
super(scriptEngineManager);
}
protected void configureScriptEngines(String language, ScriptEngine scriptEngine) {
super.configureScriptEngines(language, scriptEngine);
if (ScriptingEngines.GROOVY_SCRIPTING_LANGUAGE.equals(language)) {
// make sure Groovy compiled scripts only hold weak references to java methods
scriptEngine.getContext().setAttribute("#jsr223.groovy.engine.keep.globals", "soft", ScriptContext.ENGINE_SCOPE);
}
}
}
Если нужна большая гибкость в конфигурировании script engine, можно переопределить метод уровнем выше в цепочке создания script engine или предоставить собственную реализацию интерфейса. Ниже пример, который создает пользовательский экземпляр GraalVM JavaScript с включенным Nashorn Compatibility Mode:
public class CustomScriptEngineResolver extends DefaultScriptEngineResolver {
public CustomScriptEngineResolver(ScriptEngineManager scriptEngineManager) {
super(scriptEngineManager);
}
@Override
protected void configureGraalJsScriptEngine(ScriptEngine scriptEngine) {
// do nothing
}
@Override
protected ScriptEngine getJavaScriptScriptEngine(String language) {
return com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.create(null,
org.graalvm.polyglot.Context.newBuilder("js")
// make sure GraalVM JS can provide access to the host and can lookup classes
.allowHostClassLookup(s -> true)
// enable Nashorn Compatibility Mode
.allowExperimentalOptions(true)
.option("js.nashorn-compat", "true"));
}
}
Ссылки на классы, предоставляемые Process Application
Скрипт может ссылаться на классы, предоставленные process application, импортируя их, как в следующем примере Groovy-скрипта.
import my.process.application.CustomClass
sum = new CustomClass().calculate()
execution.setVariable('sum', sum)
Чтобы избежать возможных проблем class loading во время выполнения скрипта, рекомендуется установить флаг конфигурации process engine enableFetchScriptEngineFromProcessApplication в true.
Обратите внимание: флаг enableFetchScriptEngineFromProcessApplication релевантен только для сценария shared engine.
Переменные, доступные во время выполнения скрипта
Во время выполнения скриптов доступны все переменные процесса, видимые в текущем scope.
К ним можно обращаться напрямую по имени переменной (например, sum). Это не относится к
JRuby, где нужно обращаться к переменной как к глобальной ruby-переменной (с префиксом $,
например, $sum).
Также есть специальные переменные:
-
execution, всегда доступная, если скрипт выполняется в execution scope (например, в script task) ({{< javadocref page="org/camunda/bpm/engine/delegate/DelegateExecution.html" text="DelegateExecution" >}}). -
task, доступная, если скрипт выполняется в task scope (например, в task listener) ({{< javadocref page="org/camunda/bpm/engine/delegate/DelegateTask.html" text="DelegateTask" >}}). -
connector, доступная, если скрипт выполняется в scope переменных connector (например, outputParameter у camunda:connector) ({{< javadocref page="org/camunda/connect/plugin/impl/ConnectorVariableScope.html" text="ConnectorVariableScope" >}}).
Эти переменные соответствуют интерфейсам DelegateExecution, DelegateTask или ConnectorVariableScope,
что означает, что их можно использовать для получения/установки переменных или доступа к сервисам process engine.
// get process variable
sum = execution.getVariable('x')
// set process variable
execution.setVariable('y', x + 15)
// get task service and query for task
task = execution.getProcessEngineServices().getTaskService()
.createTaskQuery()
.taskDefinitionKey("task")
.singleResult()
Доступ к сервисам Process Engine из скриптов
Java API OpenBPM Engine предоставляет доступ к сервисам process engine OpenBPM Engine; к этим сервисам можно обращаться из скриптов:
{{< javadocref page="org/camunda/bpm/engine/ProcessEngineServices.html" text="Process Engine Services" >}} \
{{< javadocref page="org/camunda/bpm/engine/package-summary.html" text="Public Java API of OpenBPM Engine Engine" >}}
Пример создания BPMN Message, коррелирующегося по message key work:
execution.getProcessEngineServices().getRuntimeService().createMessageCorrelation("work").correlateWithResult();
Вывод в консоль из скриптов
Во время выполнения скриптов может понадобиться вывод в консоль для логирования и отладки.
Вот примеры, как это сделать в соответствующем языке:
-
Groovy:
println 'This prints to the console'
-
Java:
var system = java.lang.System;
system.out.println('This prints to the console');
Источник скрипта
Стандартный способ задать исходный код скрипта в BPMN XML-модели - добавить его напрямую в XML-файл. Тем не менее OpenBPM Engine предоставляет дополнительные способы задания источника скрипта.
Если вы используете язык скриптов, отличный от Expression Language, вы также можете задать источник скрипта как выражение, возвращающее исполняемый исходный код. Таким образом исходный код может, например, находиться в переменной процесса.
В следующем фрагменте примера process engine будет вычислять выражение \${sourceCode} в
текущем контексте каждый раз при выполнении элемента.
<!-- inside a script task -->
<scriptTask scriptFormat="groovy">
<script>${sourceCode}</script>
</scriptTask>
<!-- as an execution listener -->
<camunda:executionListener>
<camunda:script scriptFormat="groovy">${sourceCode}</camunda:script>
</camunda:executionListener>
<!-- as a condition expression -->
<sequenceFlow id="flow" sourceRef="theStart" targetRef="theTask">
<conditionExpression xsi:type="tFormalExpression" language="groovy">
${sourceCode}
</conditionExpression>
</sequenceFlow>
<!-- as an inputOutput mapping -->
<camunda:inputOutput>
<camunda:inputParameter name="x">
<camunda:script scriptFormat="groovy">${sourceCode}</camunda:script>
</camunda:inputParameter>
</camunda:inputOutput>
Вы также можете задать атрибут camunda:resource у элемента scriptTask и conditionExpression,
а также атрибут resource у элемента camunda:script. Этот атрибут расширения указывает путь к внешнему ресурсу,
который должен использоваться как исходный код скрипта. При необходимости путь ресурса можно
дополнить URL-подобной схемой, чтобы указать, находится ли ресурс в deployment или classpath.
Поведение по умолчанию: ресурс находится в classpath. Это означает, что первые два элемента
script task в следующем примере эквивалентны.
<!-- on a script task -->
<scriptTask scriptFormat="groovy" camunda:resource="org/camunda/bpm/task.groovy"/>
<scriptTask scriptFormat="groovy" camunda:resource="classpath://org/camunda/bpm/task.groovy"/>
<scriptTask scriptFormat="groovy" camunda:resource="deployment://org/camunda/bpm/task.groovy"/>
<!-- in an execution listener -->
<camunda:executionListener>
<camunda:script scriptFormat="groovy" resource="deployment://org/camunda/bpm/listener.groovy"/>
</camunda:executionListener>
<!-- on a conditionExpression -->
<conditionExpression xsi:type="tFormalExpression" language="groovy"
camunda:resource="org/camunda/bpm/condition.groovy" />
<!-- in an inputParameter -->
<camunda:inputParameter name="x">
<camunda:script scriptFormat="groovy" resource="org/camunda/bpm/mapX.groovy" />
</camunda:inputParameter>
Путь ресурса также можно задать как выражение, вычисляемое при вызове script task.
<scriptTask scriptFormat="groovy" camunda:resource="${scriptPath}"/>
Подробнее см. в разделе camunda:resource главы Custom Extensions.
Особенности JavaScript
Выполнение JavaScript-кода является частью Java Runtime (JRE) через script engine Nashorn до Java 14, и поэтому «из коробки» доступно только там. Мы включаем GraalVM JavaScript в предсобранные дистрибутивы OpenBPM Engine как замену независимо от версии JRE. Если этот script engine доступен, в контексте process engine выполнение JavaScript-кода предпочтительно идет через GraalVM JavaScript. Если script engine не найден, process engine по умолчанию позволяет JVM выбрать подходящий script engine.
Вы можете задать JavaScript engine по умолчанию для языков javascript и ecmascript с помощью свойства конфигурации process engine scriptEngineNameJavaScript.
Установите это значение в nashorn, чтобы настроить process engine на использование Nashorn по умолчанию.
Обратите внимание: если script engine, соответствующий этому значению, не найден, process engine не ищет fallback-вариант и выбрасывает исключение.
По вопросам этого script engine см. официальный GraalVM JavaScript Guide https://www.graalvm.org/reference-manual/js/ScriptEngine/. Там же есть руководство по Migration from Nashorn https://www.graalvm.org/reference-manual/js/NashornMigrationGuide/.
Лицензия и атрибуция
Эта документация была создана на базе материала "Camunda 7 Docs" от Camunda, находится под лицензией Creative Commons Attribution-ShareAlike 3.0 Unported License .
Оригинал документации: https://docs.camunda.org