diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 2fe8b27..ea27500 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -56,7 +56,7 @@ jobs:
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ${{ steps.release.outputs.artifacts_archive_path }}
- asset_name: camunda-7-to-8-migration.zip
+ asset_name: script-connector.zip
asset_content_type: application/zip
- name: Publish Unit Test Results
id: publish
diff --git a/.gitignore b/.gitignore
index 4213124..9b09632 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,11 +21,11 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
-/.classpath
-/.project
-/.settings
-/target
-
-# intellij
+target
.idea
*.iml
+
+#eclipse
+**.project
+**.classpath
+**.settings
diff --git a/README.md b/README.md
index 197890b..c51e9b0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# zeebe-script-worker
+# Skript Connector
[![](https://img.shields.io/badge/Community%20Extension-An%20open%20source%20community%20maintained%20project-FF4700)](https://github.com/camunda-community-hub/community)
[![](https://img.shields.io/badge/Lifecycle-Stable-brightgreen)](https://github.com/Camunda-Community-Hub/community/blob/main/extension-lifecycle.md#stable-)
@@ -6,12 +6,17 @@
[![Compatible with: Camunda Platform 8](https://img.shields.io/badge/Compatible%20with-Camunda%20Platform%208-0072Ce)](https://github.com/camunda-community-hub/community/blob/main/extension-lifecycle.md#compatiblilty)
-
-_This is a community project meant for playing around with Zeebe. It is not officially supported by the Zeebe Team (i.e. no gurantees). Everybody is invited to contribute!_
-A Zeebe worker to evaluate scripts (i.e. script tasks). Scripts are useful for prototyping, to do (simple) calculations, or creating/modifying variables.
+_This is a community project that provides a connector. It is not officially supported by Camunda. Everybody is invited to contribute!_
+A connector to evaluate scripts (i.e. script tasks) that are not written in FEEL. Scripts are useful for prototyping, to do (simple) calculations, or creating/modifying variables.
## Usage
+### Legacy
+
+The legacy connector provides compatibility with the previous implementation `zeebe-script-worker`.
+
+>The context does not offer access to `job` or `zeebeClient` anymore.
+
Example BPMN with service task:
```xml
@@ -21,6 +26,7 @@ Example BPMN with service task:
+
@@ -30,67 +36,67 @@ Example BPMN with service task:
* required custom headers:
* `language` - the name of the script language
* `script` - the script to evaluate
-* available context/variables in script:
- * `job` (ActivatedJob) - the current job
- * `zeebeClient` (ZeebeClient) - the client of the worker
-* the result of the evaluation is passed as `result` variable
+ * `resultVariable` - the result of the evaluation is passed to this variable
+
+### Connector
-Available script languages:
+The connector provides an [element template](./connector/element-templates/script-connector.json) that can be used to configure it.
+
+### Script languages
+
+Available script languages are by default:
* [javascript](https://www.graalvm.org/) (GraalVM JS)
* [groovy](http://groovy-lang.org/)
* [mustache](http://mustache.github.io/mustache.5.html)
* [kotlin](https://kotlinlang.org/)
+To register new script languages, you can use the `ScriptEngineFactory` to register any JSR-223 compliant script engine.
+
+If you want to provide a non-compliant implementation, you can use the [`ScriptEvaluatorExtension`](./connector/src/main/java/io/camunda/community/connector/script/spi/ScriptEvaluatorExtension.java) SPI.
+
+To register custom file extensions, you can use the [`LanguageProviderExtension`](./connector/src/main/java/io/camunda/community/connector/script/spi/LanguageProviderExtension.java) SPI.
+
## Install
### Docker
-The docker image for the worker is published on [GitHub Packages](https://github.com/orgs/camunda-community-hub/packages/container/package/zeebe-script-worker).
+For a local setup, the repository contains a [docker-compose file](docker/docker-compose.yml). It starts a Zeebe broker and both (standalone and bundled) containers.
```
-docker pull ghcr.io/camunda-community-hub/zeebe-script-worker:1.2.0
+mvn clean package
+cd docker
+docker-compose up
```
-* configure the connection to the Zeebe broker by setting `zeebe.client.broker.contactPoint` (default: `localhost:26500`)
-For a local setup, the repository contains a [docker-compose file](docker/docker-compose.yml). It starts a Zeebe broker and the worker.
+#### Standalone Runtime
+
+The docker image for the connector runtime is published as GitHub package.
```
-cd docker
-docker-compose up
+docker pull ghcr.io/camunda-community-hub/script-connector/runtime:latest
```
-### Manual
+Configure the connection to the Zeebe broker by setting the environment property `ZEEBE_CLIENT_BROKER_GATEWAY-ADDRESS` (default: `localhost:26500`)
-1. Download the latest [worker JAR](https://github.com/zeebe-io/zeebe-script-worker/releases) _(zeebe-script-worker-%{VERSION}.jar
-)_
+The docker-compose file shows an example how this works.
-1. Start the worker
- `java -jar zeebe-script-worker-{VERSION}.jar`
+#### Bundled Runtime
-### Configuration
+To run the connector inside the bundle, you can use the shaded jar.
-The worker is a Spring Boot application that uses the [Spring Zeebe Starter](https://github.com/zeebe-io/spring-zeebe). The configuration can be changed via environment variables or an `application.yaml` file. See also the following resources:
-* [Spring Zeebe Configuration](https://github.com/zeebe-io/spring-zeebe#configuring-zeebe-connection)
-* [Spring Boot Configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config)
+The docker-compose file shows an example how this works.
-```
-zeebe:
- client:
- worker:
- defaultName: script-worker
- defaultType: script
- threads: 3
-
- job.timeout: 10000
- broker.contactPoint: 127.0.0.1:26500
- security.plaintext: true
-```
+### Manual
+
+#### Standalone Runtime
-## Build from Source
+1. Download the runtime jar `script-connector-runtime-{VERSION}.jar`
+2. Start the connector runtime `java -jar script-connector-runtime-{VERSION}.jar`
-Build with Maven
+#### Bundled Runtime
-`mvn clean install`
+1. Download the shaded connector jar `script-connector-{VERSION}-shaded.jar`
+2. Copy it to your connector runtime.
## Code of Conduct
diff --git a/connector/element-templates/script-connector.json b/connector/element-templates/script-connector.json
new file mode 100644
index 0000000..099623b
--- /dev/null
+++ b/connector/element-templates/script-connector.json
@@ -0,0 +1,163 @@
+{
+ "$schema" : "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json",
+ "name" : "Script Connector",
+ "id" : "io.camunda.community:script-connector",
+ "description" : "A connector to execute a script",
+ "version" : 1,
+ "category" : {
+ "id" : "connectors",
+ "name" : "Connectors"
+ },
+ "appliesTo" : [ "bpmn:Task" ],
+ "elementType" : {
+ "value" : "bpmn:ServiceTask"
+ },
+ "groups" : [ {
+ "id" : "default",
+ "label" : "Properties"
+ }, {
+ "id" : "output",
+ "label" : "Output mapping"
+ }, {
+ "id" : "error",
+ "label" : "Error handling"
+ }, {
+ "id" : "retries",
+ "label" : "Retries"
+ } ],
+ "properties" : [ {
+ "value" : "io.camunda.community:script-connector",
+ "binding" : {
+ "type" : "zeebe:taskDefinition:type"
+ },
+ "type" : "Hidden"
+ }, {
+ "id" : "script.type",
+ "label" : "Type",
+ "group" : "default",
+ "binding" : {
+ "name" : "script.type",
+ "type" : "zeebe:input"
+ },
+ "type" : "Dropdown",
+ "choices" : [ {
+ "name" : "Embedded",
+ "value" : "embedded"
+ }, {
+ "name" : "Resource",
+ "value" : "resource"
+ } ]
+ }, {
+ "id" : "script.embedded",
+ "label" : "Script",
+ "description" : "The script to be executed",
+ "optional" : false,
+ "constraints" : {
+ "notEmpty" : true
+ },
+ "feel" : "optional",
+ "group" : "default",
+ "binding" : {
+ "name" : "script.embedded",
+ "type" : "zeebe:input"
+ },
+ "condition" : {
+ "property" : "script.type",
+ "equals" : "embedded"
+ },
+ "type" : "String"
+ }, {
+ "id" : "script.language",
+ "label" : "Script Language",
+ "description" : "The language the script uses. By default, the ones available are: javascript, groovy, kotlin, mustache",
+ "optional" : false,
+ "constraints" : {
+ "notEmpty" : true
+ },
+ "feel" : "optional",
+ "group" : "default",
+ "binding" : {
+ "name" : "script.language",
+ "type" : "zeebe:input"
+ },
+ "condition" : {
+ "property" : "script.type",
+ "equals" : "embedded"
+ },
+ "type" : "String"
+ }, {
+ "id" : "script.resource",
+ "label" : "Script resource",
+ "description" : "The resource that should be executed. Should be prefixed with 'classpath:' for a classpath resource, 'file:' for a file system resource. If none of these prefixes matches, it will attempt to load the provided resource as URL.",
+ "optional" : false,
+ "constraints" : {
+ "notEmpty" : true
+ },
+ "feel" : "optional",
+ "group" : "default",
+ "binding" : {
+ "name" : "script.resource",
+ "type" : "zeebe:input"
+ },
+ "condition" : {
+ "property" : "script.type",
+ "equals" : "resource"
+ },
+ "type" : "String"
+ }, {
+ "id" : "context",
+ "label" : "Script context",
+ "description" : "The context that is available to the script",
+ "optional" : false,
+ "feel" : "required",
+ "group" : "default",
+ "binding" : {
+ "name" : "context",
+ "type" : "zeebe:input"
+ },
+ "type" : "String"
+ }, {
+ "id" : "resultVariable",
+ "label" : "Result variable",
+ "description" : "Name of variable to store the response in",
+ "group" : "output",
+ "binding" : {
+ "key" : "resultVariable",
+ "type" : "zeebe:taskHeader"
+ },
+ "type" : "String"
+ }, {
+ "id" : "resultExpression",
+ "label" : "Result expression",
+ "description" : "Expression to map the response into process variables",
+ "feel" : "required",
+ "group" : "output",
+ "binding" : {
+ "key" : "resultExpression",
+ "type" : "zeebe:taskHeader"
+ },
+ "type" : "Text"
+ }, {
+ "id" : "errorExpression",
+ "label" : "Error expression",
+ "description" : "Expression to handle errors. Details in the documentation.",
+ "feel" : "required",
+ "group" : "error",
+ "binding" : {
+ "key" : "errorExpression",
+ "type" : "zeebe:taskHeader"
+ },
+ "type" : "Text"
+ }, {
+ "id" : "retryBackoff",
+ "label" : "Retry backoff",
+ "description" : "ISO-8601 duration to wait between retries",
+ "value" : "PT0S",
+ "group" : "retries",
+ "binding" : {
+ "key" : "retryBackoff",
+ "type" : "zeebe:taskHeader"
+ },
+ "type" : "Hidden"
+ } ]
+}
\ No newline at end of file
diff --git a/connector/pom.xml b/connector/pom.xml
new file mode 100644
index 0000000..0b6c1cc
--- /dev/null
+++ b/connector/pom.xml
@@ -0,0 +1,113 @@
+
+
+ 4.0.0
+ Script Connector
+ script-connector
+
+
+ io.camunda.connectors.community
+ script-connector-parent
+ 1.2.1-SNAPSHOT
+
+
+
+
+ io.camunda.connector
+ connector-core
+ provided
+
+
+ io.camunda.connector
+ element-template-generator
+ true
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ provided
+
+
+
+
+ com.samskivert
+ jmustache
+
+
+ org.codehaus.groovy
+ groovy-all
+
+
+ org.graalvm.js
+ js
+
+
+ org.graalvm.js
+ js-scriptengine
+
+
+ org.jetbrains.kotlin
+ kotlin-scripting-jsr223
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ io.camunda.spring
+ spring-boot-starter-camunda-test
+ test
+
+
+ io.camunda.connector
+ spring-boot-starter-camunda-connectors
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+ true
+ false
+
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ io.camunda.connector
+ element-template-generator-maven-plugin
+
+
+ io.camunda.community.connector.script.ScriptConnector
+
+
+
+
+
+
diff --git a/connector/src/main/java/io/camunda/community/connector/script/LanguageProvider.java b/connector/src/main/java/io/camunda/community/connector/script/LanguageProvider.java
new file mode 100644
index 0000000..ae80a83
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/LanguageProvider.java
@@ -0,0 +1,30 @@
+package io.camunda.community.connector.script;
+
+import io.camunda.community.connector.script.spi.LanguageProviderExtension;
+import java.util.Properties;
+
+public class LanguageProvider {
+
+ private final Properties properties;
+
+ public LanguageProvider() {
+ properties = new Properties();
+ LanguageProviderExtension.load().forEach(e -> properties.putAll(e.getLanguages()));
+ }
+
+ public LanguageProvider(Properties properties) {
+ this();
+ this.properties.putAll(properties);
+ }
+
+ public String getLanguageForScriptResource(String scriptResource) {
+ String fileExtension = scriptResource.substring(scriptResource.lastIndexOf(".") + 1);
+ String language = properties.getProperty(fileExtension);
+ if (language == null) {
+ throw new IllegalStateException(
+ String.format(
+ "Could not determine script language from file suffix '%s'", fileExtension));
+ }
+ return language;
+ }
+}
diff --git a/connector/src/main/java/io/camunda/community/connector/script/ScriptConnector.java b/connector/src/main/java/io/camunda/community/connector/script/ScriptConnector.java
new file mode 100644
index 0000000..cd10cfa
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/ScriptConnector.java
@@ -0,0 +1,73 @@
+package io.camunda.community.connector.script;
+
+import io.camunda.community.connector.script.ScriptConnectorInput.Type;
+import io.camunda.community.connector.script.ScriptConnectorInput.Type.Embedded;
+import io.camunda.community.connector.script.ScriptConnectorInput.Type.Resource;
+import io.camunda.connector.api.annotation.OutboundConnector;
+import io.camunda.connector.api.outbound.OutboundConnectorContext;
+import io.camunda.connector.api.outbound.OutboundConnectorFunction;
+import io.camunda.connector.generator.annotation.ElementTemplate;
+
+@OutboundConnector(
+ type = ScriptConnector.SCRIPT_CONNECTOR_TYPE,
+ name = "script-connector",
+ inputVariables = {"script", "context"})
+@ElementTemplate(
+ id = ScriptConnector.SCRIPT_CONNECTOR_TYPE,
+ name = "Script Connector",
+ version = 1,
+ inputDataClass = ScriptConnectorInput.class,
+ description = "A connector to execute a script")
+public class ScriptConnector implements OutboundConnectorFunction {
+ public static final String SCRIPT_CONNECTOR_TYPE = "io.camunda.community:script-connector";
+
+ private final ScriptEvaluator scriptEvaluator;
+ private final ScriptResourceProvider scriptResourceProvider;
+ private final LanguageProvider languageProvider;
+
+ public ScriptConnector() {
+ scriptEvaluator = new ScriptEvaluator();
+ scriptResourceProvider = new ScriptResourceProvider();
+ languageProvider = new LanguageProvider();
+ }
+
+ public ScriptConnector(
+ ScriptEvaluator scriptEvaluator,
+ ScriptResourceProvider scriptResourceProvider,
+ LanguageProvider languageProvider) {
+ this.scriptEvaluator = scriptEvaluator;
+ this.scriptResourceProvider = scriptResourceProvider;
+ this.languageProvider = languageProvider;
+ }
+
+ @Override
+ public Object execute(OutboundConnectorContext outboundConnectorContext) {
+ ScriptConnectorInput scriptConnectorInput =
+ outboundConnectorContext.bindVariables(ScriptConnectorInput.class);
+ String script = extractScript(scriptConnectorInput);
+ String language = extractLanguage(scriptConnectorInput);
+ return scriptEvaluator.evaluate(language, script, scriptConnectorInput.context());
+ }
+
+ private String extractLanguage(ScriptConnectorInput scriptConnectorInput) {
+ Type script = scriptConnectorInput.script();
+ if (script instanceof Embedded e) {
+ return e.language();
+ } else if (script instanceof Resource r) {
+ return languageProvider.getLanguageForScriptResource(r.resource());
+ } else {
+ throw new IllegalStateException("No script or resource has been provided");
+ }
+ }
+
+ private String extractScript(ScriptConnectorInput scriptConnectorInput) {
+ Type script = scriptConnectorInput.script();
+ if (script instanceof Embedded e) {
+ return e.embedded();
+ } else if (script instanceof Resource r) {
+ return scriptResourceProvider.provideScript(r.resource());
+ } else {
+ throw new IllegalStateException("No script or resource has been provided");
+ }
+ }
+}
diff --git a/connector/src/main/java/io/camunda/community/connector/script/ScriptConnectorInput.java b/connector/src/main/java/io/camunda/community/connector/script/ScriptConnectorInput.java
new file mode 100644
index 0000000..9b6dffd
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/ScriptConnectorInput.java
@@ -0,0 +1,53 @@
+package io.camunda.community.connector.script;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import io.camunda.community.connector.script.ScriptConnectorInput.Type.Embedded;
+import io.camunda.community.connector.script.ScriptConnectorInput.Type.Resource;
+import io.camunda.connector.generator.annotation.TemplateProperty;
+import io.camunda.connector.generator.dsl.Property.FeelMode;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import java.util.Map;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record ScriptConnectorInput(
+ @TemplateProperty(label = "Script description", description = "How the script is implemented")
+ @NotNull
+ @Valid
+ Type script,
+ @TemplateProperty(
+ label = "Script context",
+ feel = FeelMode.required,
+ description = "The context that is available to the script")
+ Map context) {
+
+ @JsonTypeInfo(use = Id.NAME, property = "type")
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = Embedded.class, name = "embedded"),
+ @JsonSubTypes.Type(value = Resource.class, name = "resource")
+ })
+ sealed interface Type {
+ record Embedded(
+ @TemplateProperty(label = "Script", description = "The script to be executed") @NotNull
+ String embedded,
+ @TemplateProperty(
+ label = "Script Language",
+ description =
+ "The language the script uses. By default, the ones available are: javascript, groovy, kotlin, mustache")
+ @NotNull
+ String language)
+ implements Type {}
+
+ record Resource(
+ @TemplateProperty(
+ label = "Script resource",
+ description =
+ "The resource that should be executed. Should be prefixed with 'classpath:' for a classpath resource, 'file:' for a file system resource. If none of these prefixes matches, it will attempt to load the provided resource as URL.")
+ @NotNull
+ String resource)
+ implements Type {}
+ }
+}
diff --git a/connector/src/main/java/io/camunda/community/connector/script/ScriptConnectorLegacy.java b/connector/src/main/java/io/camunda/community/connector/script/ScriptConnectorLegacy.java
new file mode 100644
index 0000000..cad0647
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/ScriptConnectorLegacy.java
@@ -0,0 +1,57 @@
+package io.camunda.community.connector.script;
+
+import static java.util.Optional.*;
+
+import io.camunda.connector.api.annotation.OutboundConnector;
+import io.camunda.connector.api.outbound.OutboundConnectorContext;
+import io.camunda.connector.api.outbound.OutboundConnectorFunction;
+import java.util.Map;
+
+@OutboundConnector(
+ type = "script",
+ name = "script-connector-legacy",
+ inputVariables = {})
+public class ScriptConnectorLegacy implements OutboundConnectorFunction {
+ private static final String PARAM_LANGUAGE = "language";
+ private static final String PARAM_SCRIPT_FORMAT = "scriptFormat";
+ private static final String PARAM_HEADER = "script";
+
+ private final ScriptEvaluator scriptEvaluator = new ScriptEvaluator();
+
+ @Override
+ public Object execute(OutboundConnectorContext outboundConnectorContext) throws Exception {
+ // do not leave behind the old party
+ final Map customHeaders =
+ outboundConnectorContext.getJobContext().getCustomHeaders();
+ final String language = getLanguage(customHeaders);
+ final String script = getScript(customHeaders);
+
+ final Map variables = getVariablesAsMap(outboundConnectorContext);
+
+ return scriptEvaluator.evaluate(language, script, variables);
+ }
+
+ private String getLanguage(Map customHeaders) {
+ return ofNullable(customHeaders.get(PARAM_SCRIPT_FORMAT))
+ .orElseGet(
+ () ->
+ ofNullable(customHeaders.get(PARAM_LANGUAGE))
+ .orElseThrow(
+ () ->
+ new RuntimeException(
+ String.format(
+ "Missing required custom header '%s'", PARAM_LANGUAGE))));
+ }
+
+ private String getScript(Map customHeaders) {
+ return ofNullable(customHeaders.get(PARAM_HEADER))
+ .orElseThrow(
+ () ->
+ new RuntimeException(
+ String.format("Missing required custom header '%s'", PARAM_HEADER)));
+ }
+
+ private Map getVariablesAsMap(OutboundConnectorContext outboundConnectorContext) {
+ return (Map) outboundConnectorContext.bindVariables(Map.class);
+ }
+}
diff --git a/src/main/java/io/zeebe/script/ScriptEvaluator.java b/connector/src/main/java/io/camunda/community/connector/script/ScriptEvaluator.java
similarity index 56%
rename from src/main/java/io/zeebe/script/ScriptEvaluator.java
rename to connector/src/main/java/io/camunda/community/connector/script/ScriptEvaluator.java
index c2a53f0..962c023 100644
--- a/src/main/java/io/zeebe/script/ScriptEvaluator.java
+++ b/connector/src/main/java/io/camunda/community/connector/script/ScriptEvaluator.java
@@ -13,34 +13,46 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.zeebe.script;
+package io.camunda.community.connector.script;
+import io.camunda.community.connector.script.spi.ScriptEvaluatorExtension;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
-import org.springframework.stereotype.Component;
-@Component
public class ScriptEvaluator {
private final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
- private final Map additionalEvaluators =
- Map.of("mustache", new MustacheEvaluator());
-
private final Map cachedScriptEngines = new HashMap<>();
- private final GraalEvaluator graalEvaluator = new GraalEvaluator();
- private final ScriptEngineEvaluator scriptEngineEvaluator = new ScriptEngineEvaluator();
+ private final Map scriptEvaluatorExtensions = new HashMap<>();
+
+ public ScriptEvaluator() {
+ ScriptEvaluatorExtension.load()
+ .forEach(
+ e ->
+ e.getEvaluatedLanguage()
+ .forEach(language -> scriptEvaluatorExtensions.put(language, e)));
+ }
+
+ public ScriptEvaluator(Set extensions) {
+ this();
+ extensions.forEach(
+ e ->
+ e.getEvaluatedLanguage()
+ .forEach(language -> scriptEvaluatorExtensions.put(language, e)));
+ }
public Object evaluate(String language, String script, Map variables) {
- if (additionalEvaluators.containsKey(language)) {
- final var scriptEvaluator = additionalEvaluators.get(language);
- return scriptEvaluator.eval(script, variables);
+ if (scriptEvaluatorExtensions.containsKey(language)) {
+ final var scriptEvaluator = scriptEvaluatorExtensions.get(language);
+ return scriptEvaluator.evaluateScript(script, variables);
}
return evalWithScriptEngine(language, script, variables);
@@ -49,24 +61,21 @@ public Object evaluate(String language, String script, Map varia
private Object evalWithScriptEngine(
String language, String script, Map variables) {
try {
- if (GraalEvaluator.SUPPORTED_LANGUAGES.contains(language)) {
- return graalEvaluator.evaluate(language, script, variables);
- } else {
- final ScriptEngine scriptEngine =
- cachedScriptEngines.computeIfAbsent(language, scriptEngineManager::getEngineByName);
- if (scriptEngine == null) {
- final String msg = String.format("No script engine found with name '%s'", language);
- throw new RuntimeException(msg);
- }
- return eval(scriptEngine, script, variables);
+ final ScriptEngine scriptEngine =
+ cachedScriptEngines.computeIfAbsent(language, scriptEngineManager::getEngineByName);
+ if (scriptEngine == null) {
+ final String msg = String.format("No script engine found with name '%s'", language);
+ throw new RuntimeException(msg);
}
+ return eval(scriptEngine, script, variables);
} catch (Exception e) {
final String msg = String.format("Failed to evaluate script '%s' (%s)", script, language);
throw new RuntimeException(msg, e);
}
}
- private Object eval(ScriptEngine scriptEngine, String script, Map variables)
+ private synchronized Object eval(
+ ScriptEngine scriptEngine, String script, Map variables)
throws ScriptException {
final ScriptContext context = scriptEngine.getContext();
diff --git a/connector/src/main/java/io/camunda/community/connector/script/ScriptResourceProvider.java b/connector/src/main/java/io/camunda/community/connector/script/ScriptResourceProvider.java
new file mode 100644
index 0000000..61db700
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/ScriptResourceProvider.java
@@ -0,0 +1,76 @@
+package io.camunda.community.connector.script;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.function.Function;
+
+public class ScriptResourceProvider {
+ private final ResourceLoader[] resourceLoaders =
+ new ResourceLoader[] {
+ new ResourceLoader("classpath", this::loadFromClassPath),
+ new ResourceLoader("file", this::loadFromFile)
+ };
+
+ public String provideScript(String scriptResource) {
+ if (scriptResource == null) {
+ throw new IllegalStateException("scriptResource must not be null");
+ }
+ for (ResourceLoader loader : resourceLoaders) {
+ if (is(loader.identifier(), scriptResource)) {
+ return loader
+ .strategy()
+ .apply(scriptResource.substring(loader.identifier().length() + 1).trim());
+ }
+ }
+ return loadFromUrl(scriptResource);
+ }
+
+ private boolean is(String resourceType, String scriptResource) {
+ return scriptResource.startsWith(resourceType + ":");
+ }
+
+ private String loadFromClassPath(String scriptResource) {
+ try (InputStream in = this.getClass().getClassLoader().getResourceAsStream(scriptResource)) {
+ if (in == null) {
+ throw new NullPointerException(String.format("No resource found for '%s'", scriptResource));
+ }
+ return new String(in.readAllBytes());
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "An exception happened while loading resource '%s' from the classpath",
+ scriptResource),
+ e);
+ }
+ }
+
+ private String loadFromFile(String scriptResource) {
+ try {
+ return new String(Files.readAllBytes(new File(scriptResource).toPath()));
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "An exception happened while loading resource '%s' from the file system",
+ scriptResource),
+ e);
+ }
+ }
+
+ private String loadFromUrl(String scriptResource) {
+ try {
+ URL scriptUrl = new URL(scriptResource);
+ try (InputStream in = scriptUrl.openStream()) {
+ return new String(in.readAllBytes());
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "An exception happened while loading resource '%s' from a URL", scriptResource),
+ e);
+ }
+ }
+
+ private record ResourceLoader(String identifier, Function strategy) {}
+}
diff --git a/connector/src/main/java/io/camunda/community/connector/script/spi/LanguageProviderExtension.java b/connector/src/main/java/io/camunda/community/connector/script/spi/LanguageProviderExtension.java
new file mode 100644
index 0000000..018ee4f
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/spi/LanguageProviderExtension.java
@@ -0,0 +1,14 @@
+package io.camunda.community.connector.script.spi;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.ServiceLoader;
+import java.util.ServiceLoader.Provider;
+
+public interface LanguageProviderExtension {
+ static List load() {
+ return ServiceLoader.load(LanguageProviderExtension.class).stream().map(Provider::get).toList();
+ }
+
+ Properties getLanguages();
+}
diff --git a/connector/src/main/java/io/camunda/community/connector/script/spi/ScriptEvaluatorExtension.java b/connector/src/main/java/io/camunda/community/connector/script/spi/ScriptEvaluatorExtension.java
new file mode 100644
index 0000000..53889f2
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/spi/ScriptEvaluatorExtension.java
@@ -0,0 +1,17 @@
+package io.camunda.community.connector.script.spi;
+
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.ServiceLoader.Provider;
+import java.util.Set;
+
+public interface ScriptEvaluatorExtension {
+ static List load() {
+ return ServiceLoader.load(ScriptEvaluatorExtension.class).stream().map(Provider::get).toList();
+ }
+
+ Set getEvaluatedLanguage();
+
+ Object evaluateScript(String script, Map context);
+}
diff --git a/connector/src/main/java/io/camunda/community/connector/script/spi/impl/DefaultLanguageProviderExtension.java b/connector/src/main/java/io/camunda/community/connector/script/spi/impl/DefaultLanguageProviderExtension.java
new file mode 100644
index 0000000..9aab06e
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/spi/impl/DefaultLanguageProviderExtension.java
@@ -0,0 +1,29 @@
+package io.camunda.community.connector.script.spi.impl;
+
+import io.camunda.community.connector.script.spi.LanguageProviderExtension;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class DefaultLanguageProviderExtension implements LanguageProviderExtension {
+ private static final String RESOURCE = "script-resource-extensions.properties";
+ private Properties properties;
+
+ @Override
+ public Properties getLanguages() {
+ if (properties == null) {
+ loadProperties();
+ }
+ return properties;
+ }
+
+ private void loadProperties() {
+ properties = new Properties();
+ try (InputStream in = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
+ properties.load(in);
+ } catch (IOException e) {
+ throw new RuntimeException(
+ String.format("Unable to load '%s' from the classpath", RESOURCE), e);
+ }
+ }
+}
diff --git a/connector/src/main/java/io/camunda/community/connector/script/spi/impl/MustacheEvaluatorExtension.java b/connector/src/main/java/io/camunda/community/connector/script/spi/impl/MustacheEvaluatorExtension.java
new file mode 100644
index 0000000..24a821e
--- /dev/null
+++ b/connector/src/main/java/io/camunda/community/connector/script/spi/impl/MustacheEvaluatorExtension.java
@@ -0,0 +1,20 @@
+package io.camunda.community.connector.script.spi.impl;
+
+import com.samskivert.mustache.Mustache;
+import com.samskivert.mustache.Template;
+import io.camunda.community.connector.script.spi.ScriptEvaluatorExtension;
+import java.util.Map;
+import java.util.Set;
+
+public class MustacheEvaluatorExtension implements ScriptEvaluatorExtension {
+ @Override
+ public Set getEvaluatedLanguage() {
+ return Set.of("mustache");
+ }
+
+ @Override
+ public Object evaluateScript(String script, Map context) {
+ final Template template = Mustache.compiler().compile(script);
+ return template.execute(context);
+ }
+}
diff --git a/connector/src/main/resources/META-INF/services/io.camunda.community.connector.script.spi.LanguageProviderExtension b/connector/src/main/resources/META-INF/services/io.camunda.community.connector.script.spi.LanguageProviderExtension
new file mode 100644
index 0000000..06a77e5
--- /dev/null
+++ b/connector/src/main/resources/META-INF/services/io.camunda.community.connector.script.spi.LanguageProviderExtension
@@ -0,0 +1 @@
+io.camunda.community.connector.script.spi.impl.DefaultLanguageProviderExtension
\ No newline at end of file
diff --git a/connector/src/main/resources/META-INF/services/io.camunda.community.connector.script.spi.ScriptEvaluatorExtension b/connector/src/main/resources/META-INF/services/io.camunda.community.connector.script.spi.ScriptEvaluatorExtension
new file mode 100644
index 0000000..0e24ab9
--- /dev/null
+++ b/connector/src/main/resources/META-INF/services/io.camunda.community.connector.script.spi.ScriptEvaluatorExtension
@@ -0,0 +1 @@
+io.camunda.community.connector.script.spi.impl.MustacheEvaluatorExtension
\ No newline at end of file
diff --git a/connector/src/main/resources/META-INF/services/io.camunda.connector.api.outbound.OutboundConnectorFunction b/connector/src/main/resources/META-INF/services/io.camunda.connector.api.outbound.OutboundConnectorFunction
new file mode 100644
index 0000000..62dcd73
--- /dev/null
+++ b/connector/src/main/resources/META-INF/services/io.camunda.connector.api.outbound.OutboundConnectorFunction
@@ -0,0 +1,2 @@
+io.camunda.community.connector.script.ScriptConnector
+io.camunda.community.connector.script.ScriptConnectorLegacy
\ No newline at end of file
diff --git a/connector/src/main/resources/script-resource-extensions.properties b/connector/src/main/resources/script-resource-extensions.properties
new file mode 100644
index 0000000..9ce05b2
--- /dev/null
+++ b/connector/src/main/resources/script-resource-extensions.properties
@@ -0,0 +1,11 @@
+# javascript
+js=javascript
+# groovy
+groovy=groovy
+gvy=groovy
+gy=groovy
+gsh=groovy
+# mustache
+mustache=mustache
+# kotlin
+kt=kotlin
\ No newline at end of file
diff --git a/src/test/java/io/zeebe/script/EvaluationGroovyTest.java b/connector/src/test/java/io/camunda/community/connector/script/EvaluationGroovyTest.java
similarity index 98%
rename from src/test/java/io/zeebe/script/EvaluationGroovyTest.java
rename to connector/src/test/java/io/camunda/community/connector/script/EvaluationGroovyTest.java
index 6c557a4..1bf7acc 100644
--- a/src/test/java/io/zeebe/script/EvaluationGroovyTest.java
+++ b/connector/src/test/java/io/camunda/community/connector/script/EvaluationGroovyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.zeebe.script;
+package io.camunda.community.connector.script;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
diff --git a/src/test/java/io/zeebe/script/EvaluationJavaScriptTest.java b/connector/src/test/java/io/camunda/community/connector/script/EvaluationJavaScriptTest.java
similarity index 98%
rename from src/test/java/io/zeebe/script/EvaluationJavaScriptTest.java
rename to connector/src/test/java/io/camunda/community/connector/script/EvaluationJavaScriptTest.java
index 6f8ea60..d67d438 100644
--- a/src/test/java/io/zeebe/script/EvaluationJavaScriptTest.java
+++ b/connector/src/test/java/io/camunda/community/connector/script/EvaluationJavaScriptTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.zeebe.script;
+package io.camunda.community.connector.script;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
diff --git a/src/test/java/io/zeebe/script/EvaluationKotlinTest.java b/connector/src/test/java/io/camunda/community/connector/script/EvaluationKotlinTest.java
similarity index 97%
rename from src/test/java/io/zeebe/script/EvaluationKotlinTest.java
rename to connector/src/test/java/io/camunda/community/connector/script/EvaluationKotlinTest.java
index b22a98e..e9e284f 100644
--- a/src/test/java/io/zeebe/script/EvaluationKotlinTest.java
+++ b/connector/src/test/java/io/camunda/community/connector/script/EvaluationKotlinTest.java
@@ -1,4 +1,4 @@
-package io.zeebe.script;
+package io.camunda.community.connector.script;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
diff --git a/src/test/java/io/zeebe/script/EvaluationMustacheTest.java b/connector/src/test/java/io/camunda/community/connector/script/EvaluationMustacheTest.java
similarity index 97%
rename from src/test/java/io/zeebe/script/EvaluationMustacheTest.java
rename to connector/src/test/java/io/camunda/community/connector/script/EvaluationMustacheTest.java
index 2a4cfeb..e54e4b3 100644
--- a/src/test/java/io/zeebe/script/EvaluationMustacheTest.java
+++ b/connector/src/test/java/io/camunda/community/connector/script/EvaluationMustacheTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.zeebe.script;
+package io.camunda.community.connector.script;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/connector/src/test/java/io/camunda/community/connector/script/LanguageProviderTest.java b/connector/src/test/java/io/camunda/community/connector/script/LanguageProviderTest.java
new file mode 100644
index 0000000..e0b93b7
--- /dev/null
+++ b/connector/src/test/java/io/camunda/community/connector/script/LanguageProviderTest.java
@@ -0,0 +1,14 @@
+package io.camunda.community.connector.script;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+public class LanguageProviderTest {
+ @Test
+ void shouldReturnLanguage() {
+ LanguageProvider provider = new LanguageProvider();
+ String languageForScriptResource = provider.getLanguageForScriptResource("some-resource.js");
+ assertThat(languageForScriptResource).isEqualTo("javascript");
+ }
+}
diff --git a/src/test/java/io/zeebe/script/ScriptEvaluatorTest.java b/connector/src/test/java/io/camunda/community/connector/script/ScriptEvaluatorTest.java
similarity index 86%
rename from src/test/java/io/zeebe/script/ScriptEvaluatorTest.java
rename to connector/src/test/java/io/camunda/community/connector/script/ScriptEvaluatorTest.java
index aa3f8a3..f4818e5 100644
--- a/src/test/java/io/zeebe/script/ScriptEvaluatorTest.java
+++ b/connector/src/test/java/io/camunda/community/connector/script/ScriptEvaluatorTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.zeebe.script;
+package io.camunda.community.connector.script;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -39,13 +39,6 @@ public void shouldEvaluateGroovy() {
assertThat(result).isEqualTo(123);
}
- @Test
- public void shouldEvaluateFeel() {
- final Object result = scriptEvaluator.evaluate("feel", "123", Collections.emptyMap());
-
- assertThat(result).isEqualTo(123L);
- }
-
@Test
public void shouldEvaluateKotlin() {
final Object result = scriptEvaluator.evaluate("kotlin", "123", Collections.emptyMap());
@@ -71,14 +64,6 @@ public void shouldEvaluateGroovyWithVariables() {
assertThat(result).isEqualTo(123);
}
- @Test
- public void shouldEvaluateFeelWithVariables() {
-
- final Object result = scriptEvaluator.evaluate("feel", "a", Collections.singletonMap("a", 123));
-
- assertThat(result).isEqualTo(123L);
- }
-
@Test
public void shouldEvaluateKotlinWithVariables() {
final Object result =
diff --git a/connector/src/test/java/io/camunda/community/connector/script/ScriptResourceProviderTest.java b/connector/src/test/java/io/camunda/community/connector/script/ScriptResourceProviderTest.java
new file mode 100644
index 0000000..73956aa
--- /dev/null
+++ b/connector/src/test/java/io/camunda/community/connector/script/ScriptResourceProviderTest.java
@@ -0,0 +1,29 @@
+package io.camunda.community.connector.script;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+public class ScriptResourceProviderTest {
+ @Test
+ void shouldLoadClasspathResource() {
+ String scriptResource = "classpath:test-script.js";
+ ScriptResourceProvider resourceProvider = new ScriptResourceProvider();
+ String script = resourceProvider.provideScript(scriptResource);
+ assertThat(script).isEqualTo("a + b;");
+ }
+
+ @Test
+ void shouldLoadFileResource(@TempDir File directory) throws IOException {
+ File scriptFile = new File(directory, "test-script.js");
+ Files.writeString(scriptFile.toPath(), "a + b;");
+ String scriptResource = "file:" + scriptFile;
+ ScriptResourceProvider resourceProvider = new ScriptResourceProvider();
+ String script = resourceProvider.provideScript(scriptResource);
+ assertThat(script).isEqualTo("a + b;");
+ }
+}
diff --git a/src/test/java/io/zeebe/script/WorkflowTest.java b/connector/src/test/java/io/camunda/community/connector/script/WorkflowTest.java
similarity index 74%
rename from src/test/java/io/zeebe/script/WorkflowTest.java
rename to connector/src/test/java/io/camunda/community/connector/script/WorkflowTest.java
index 84f71fe..7562a86 100644
--- a/src/test/java/io/zeebe/script/WorkflowTest.java
+++ b/connector/src/test/java/io/camunda/community/connector/script/WorkflowTest.java
@@ -1,5 +1,6 @@
-package io.zeebe.script;
+package io.camunda.community.connector.script;
+import static io.camunda.community.connector.script.ScriptConnector.*;
import static org.assertj.core.api.Assertions.assertThat;
import io.camunda.zeebe.client.ZeebeClient;
@@ -9,6 +10,7 @@
import io.camunda.zeebe.spring.test.ZeebeSpringTest;
import java.util.Collections;
import java.util.Map;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -30,7 +32,8 @@ public void shouldReturnResult() {
t ->
t.zeebeJobType("script")
.zeebeTaskHeader("language", "groovy")
- .zeebeTaskHeader("script", "x + 1"))
+ .zeebeTaskHeader("script", "x + 1")
+ .zeebeTaskHeader("resultVariable", "result"))
.done();
final var workflowInstanceResult =
@@ -40,6 +43,29 @@ public void shouldReturnResult() {
}
@Test
+ void shouldReturnResultConnector() {
+ BpmnModelInstance modelInstance =
+ Bpmn.createExecutableProcess("process")
+ .startEvent()
+ .scriptTask(
+ "task",
+ t ->
+ t.zeebeJobType(SCRIPT_CONNECTOR_TYPE)
+ .zeebeInput("={a:a,b:a}", "context")
+ .zeebeInput("a+b", "script.embedded")
+ .zeebeInput("embedded", "script.type")
+ .zeebeInput("javascript", "script.language")
+ .zeebeTaskHeader("resultVariable", "result"))
+ .endEvent()
+ .done();
+ final var workflowInstanceResult =
+ deployAndCreateInstance(modelInstance, Collections.singletonMap("a", 3));
+
+ assertThat(workflowInstanceResult.getVariablesAsMap()).containsEntry("result", 6);
+ }
+
+ @Test
+ @Disabled
public void shouldGetCurrentJob() {
final BpmnModelInstance workflow =
@@ -60,6 +86,7 @@ public void shouldGetCurrentJob() {
}
@Test
+ @Disabled
public void shouldUseZeebeClient() {
final String groovyScript =
"zeebeClient.newPublishMessageCommand()"
diff --git a/src/main/java/io/zeebe/script/ZeebeScriptWorkerApplication.java b/connector/src/test/java/io/camunda/community/connector/script/ZeebeScriptWorkerApplication.java
similarity index 90%
rename from src/main/java/io/zeebe/script/ZeebeScriptWorkerApplication.java
rename to connector/src/test/java/io/camunda/community/connector/script/ZeebeScriptWorkerApplication.java
index 8b3d8ec..2eb5212 100644
--- a/src/main/java/io/zeebe/script/ZeebeScriptWorkerApplication.java
+++ b/connector/src/test/java/io/camunda/community/connector/script/ZeebeScriptWorkerApplication.java
@@ -13,14 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.zeebe.script;
+package io.camunda.community.connector.script;
-import io.camunda.zeebe.spring.client.EnableZeebeClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
-@EnableZeebeClient
public class ZeebeScriptWorkerApplication {
public static void main(String[] args) {
diff --git a/connector/src/test/resources/application.yaml b/connector/src/test/resources/application.yaml
new file mode 100644
index 0000000..d28a711
--- /dev/null
+++ b/connector/src/test/resources/application.yaml
@@ -0,0 +1,7 @@
+camunda.operate.enabled: false
+camunda:
+ connector:
+ webhook:
+ enabled: false
+ polling:
+ enabled: false
\ No newline at end of file
diff --git a/connector/src/test/resources/test-script.js b/connector/src/test/resources/test-script.js
new file mode 100644
index 0000000..b321213
--- /dev/null
+++ b/connector/src/test/resources/test-script.js
@@ -0,0 +1 @@
+a + b;
\ No newline at end of file
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 47f892b..e9a1d21 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -6,8 +6,8 @@ networks:
services:
zeebe:
- container_name: zeebe_broker
- image: camunda/zeebe:8.1.5
+ container_name: zeebe
+ image: camunda/zeebe:8.3.0
environment:
- ZEEBE_LOG_LEVEL=debug
ports:
@@ -15,12 +15,30 @@ services:
- "9600:9600"
networks:
- zeebe_network
- zeebe-script-worker:
- container_name: zeebe-script-worker
- image: ghcr.io/camunda-community-hub/zeebe-script-worker:1.2.0
+ script-connector-runtime:
+ container_name: script-connector-runtime
+ image: ghcr.io/camunda-community-hub/script-connector/runtime:latest
environment:
- - zeebe.client.broker.contactPoint=zeebe:26500
+ - ZEEBE_CLIENT_BROKER_GATEWAY-ADDRESS=zeebe:26500
+ - ZEEBE_CLIENT_SECURITY_PLAINTEXT=true
depends_on:
- zeebe
networks:
- zeebe_network
+ script-connector-bundled:
+ container_name: script-connector-bundled
+ image: camunda/connectors-bundle:8.3.0
+ environment:
+ - ZEEBE_CLIENT_BROKER_GATEWAY-ADDRESS=zeebe:26500
+ - ZEEBE_CLIENT_SECURITY_PLAINTEXT=true
+ - CAMUNDA_CONNECTOR_POLLING_ENABLED=false
+ - CAMUNDA_CONNECTOR_WEBHOOK_ENABLED=false
+ - SPRING_MAIN_WEB-APPLICATION-TYPE=none
+ - OPERATE_CLIENT_ENABLED=false
+ depends_on:
+ - zeebe
+ networks:
+ - zeebe_network
+ volumes:
+ - ./../connector/target/script-connector-1.2.1-SNAPSHOT-shaded.jar:/opt/custom/script-connector.jar
+
diff --git a/pom.xml b/pom.xml
index 38ed426..4307e51 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,11 +3,15 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- Zeebe Script Worker
- io.zeebe
- zeebe-script-worker
+ Script Connector Parent
+
+ connector
+ runtime
+
+ io.camunda.connectors.community
+ script-connector-parent
1.2.1-SNAPSHOT
- jar
+ pom
org.camunda.community
@@ -26,9 +30,10 @@
${java.version}
${java.version}
- 3.1.5
- 8.2.16
- 8.2.4
+ 3.1.5
+ 8.3.0
+ 8.3.0
+ 8.3.0
2.4.21
1.9.10
@@ -39,29 +44,28 @@
3.1.1
3.3.1
3.5.1
- 3.2.1
+ 3.1.2
2.40.0
3.11.0
0.9.28
- 3.6.1
+ 3.6.0
3.12.1
3.6.0
3.0.1
1.14.2
3.3.0
+ 3.3.0
-
io.camunda
zeebe-bom
- ${version.zeebe}
+ ${version.camunda}
import
pom
-
org.jetbrains.kotlin
kotlin-bom
@@ -69,107 +73,69 @@
pom
import
-
org.springframework.boot
spring-boot-dependencies
- ${version.spring.boot}
+ ${version.spring-boot}
pom
import
-
+
+ io.camunda.connector
+ connector-core
+ ${version.camunda-connectors}
+
+
+ io.camunda.connector
+ element-template-generator
+ ${version.camunda-connectors}
+
+
+ io.camunda.spring
+ spring-boot-starter-camunda-test
+ ${version.camunda-spring}
+
+
+ io.camunda.connector
+ spring-boot-starter-camunda-connectors
+ ${version.camunda-connectors}
+
+
+ org.codehaus.groovy
+ groovy-all
+ ${version.groovy}
+
+
+ org.graalvm.js
+ js
+ ${version.graalvm}
+
+
+ org.graalvm.js
+ js-scriptengine
+ ${version.graalvm}
+
+
+ org.jetbrains.kotlin
+ kotlin-scripting-jsr223
+ ${version.kotlin}
+
+
+ io.camunda.connectors.community
+ script-connector
+ ${project.version}
+
-
-
-
-
- io.camunda
- spring-zeebe-starter
- ${version.zeebe.spring}
-
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
-
- org.springframework.boot
- spring-boot-starter-mustache
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
-
- org.codehaus.groovy
- groovy-all
- ${version.groovy}
-
-
-
- org.graalvm.sdk
- graal-sdk
- ${version.graalvm}
-
-
-
- org.graalvm.js
- js
- ${version.graalvm}
-
-
-
- org.jetbrains.kotlin
- kotlin-scripting-jsr223
- ${version.kotlin}
-
-
-
-
- org.junit.jupiter
- junit-jupiter
- 5.10.0
- test
-
-
-
- org.assertj
- assertj-core
- 3.24.2
- test
-
-
-
- org.testcontainers
- junit-jupiter
- 1.19.1
- test
-
-
-
- io.camunda
- spring-zeebe-test
- ${version.zeebe.spring}
- test
-
-
-
-
+
+ io.camunda.connector
+ element-template-generator-maven-plugin
+ ${version.camunda-connectors}
+
org.sonatype.plugins
nexus-staging-maven-plugin
@@ -259,7 +225,7 @@
-
+
@@ -281,8 +247,8 @@
*.md
.gitignore
-
-
+
+
true
2
@@ -290,9 +256,9 @@
-
+
-
+
@@ -327,7 +293,7 @@
-
+
@@ -336,67 +302,6 @@
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.2.1
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- ${version.spring.boot}
-
-
-
- repackage
-
-
-
-
-
-
-
- org.jetbrains.kotlin
- kotlin-compiler-embeddable
-
-
-
-
-
-
- com.google.cloud.tools
- jib-maven-plugin
- 3.4.0
-
-
- deploy
-
- build
-
-
-
-
-
- ghcr.io/camunda-community-hub/zeebe-script-worker
- ${project.version}
-
-
-
-
-
@@ -447,38 +352,4 @@
-
-
-
- zeebe
- Zeebe Repository
- https://artifacts.camunda.com/artifactory/zeebe-io/
-
- true
-
-
- false
-
-
-
-
- zeebe-snapshots
- Zeebe Snapshot Repository
- https://artifacts.camunda.com/artifactory/zeebe-io-snapshots/
-
- false
-
-
- true
-
-
-
-
-
- https://github.com/camunda-community-hub/zeebe-script-worker
- scm:git:git@github.com:camunda-community-hub/zeebe-script-worker.git
- scm:git:git@github.com:camunda-community-hub/zeebe-script-worker.git
- HEAD
-
-
diff --git a/runtime/pom.xml b/runtime/pom.xml
new file mode 100644
index 0000000..67604c3
--- /dev/null
+++ b/runtime/pom.xml
@@ -0,0 +1,98 @@
+
+
+ 4.0.0
+
+ io.camunda.connectors.community
+ script-connector-parent
+ 1.2.1-SNAPSHOT
+
+
+ script-connector-runtime
+ Script Connector Runtime
+
+
+
+ io.camunda.connectors.community
+ script-connector
+
+
+ io.camunda.connector
+ spring-boot-starter-camunda-connectors
+
+
+ io.camunda.spring
+ spring-boot-starter-camunda-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-compiler-embeddable
+
+
+
+
+
+
+
+
+
+ build-image
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ build-version
+
+ build-image
+
+ package
+
+
+ ghcr.io/camunda-community-hub/script-connector/runtime:${project.version}
+
+
+
+
+
+ build-latest
+
+ build-image
+
+ package
+
+
+ ghcr.io/camunda-community-hub/script-connector/runtime
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/runtime/src/main/java/io/camunda/community/connector/script/App.java b/runtime/src/main/java/io/camunda/community/connector/script/App.java
new file mode 100644
index 0000000..3ecb93a
--- /dev/null
+++ b/runtime/src/main/java/io/camunda/community/connector/script/App.java
@@ -0,0 +1,11 @@
+package io.camunda.community.connector.script;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class App {
+ public static void main(String[] args) {
+ SpringApplication.run(App.class, args);
+ }
+}
diff --git a/runtime/src/main/resources/application.yaml b/runtime/src/main/resources/application.yaml
new file mode 100644
index 0000000..f3d8fd1
--- /dev/null
+++ b/runtime/src/main/resources/application.yaml
@@ -0,0 +1,7 @@
+camunda:
+ connector:
+ polling:
+ enabled: false
+ webhook:
+ enabled: false
+
diff --git a/runtime/src/test/java/io/camunda/community/connector/script/AppTest.java b/runtime/src/test/java/io/camunda/community/connector/script/AppTest.java
new file mode 100644
index 0000000..8bf4aed
--- /dev/null
+++ b/runtime/src/test/java/io/camunda/community/connector/script/AppTest.java
@@ -0,0 +1,63 @@
+package io.camunda.community.connector.script;
+
+import static io.camunda.community.connector.script.ScriptConnector.*;
+import static org.assertj.core.api.Assertions.*;
+
+import io.camunda.zeebe.client.ZeebeClient;
+import io.camunda.zeebe.client.api.response.ProcessInstanceResult;
+import io.camunda.zeebe.model.bpmn.Bpmn;
+import io.camunda.zeebe.model.bpmn.BpmnModelInstance;
+import io.camunda.zeebe.spring.test.ZeebeSpringTest;
+import java.util.Collections;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+@ZeebeSpringTest
+public class AppTest {
+
+ @Autowired ZeebeClient zeebeClient;
+
+ @Test
+ void shouldRun() {
+ // just to assert it runs
+ }
+
+ @Test
+ void shouldExecuteConnector() {
+ BpmnModelInstance modelInstance =
+ Bpmn.createExecutableProcess("process")
+ .startEvent()
+ .scriptTask(
+ "task",
+ t ->
+ t.zeebeJobType(SCRIPT_CONNECTOR_TYPE)
+ .zeebeInput("={a:a,b:a}", "context")
+ .zeebeInput("a+b", "script.embedded")
+ .zeebeInput("embedded", "script.type")
+ .zeebeInput("javascript", "script.language")
+ .zeebeTaskHeader("resultVariable", "result"))
+ .endEvent()
+ .done();
+ final var workflowInstanceResult =
+ deployAndCreateInstance(modelInstance, Collections.singletonMap("a", 3));
+
+ assertThat(workflowInstanceResult.getVariablesAsMap()).containsEntry("result", 6);
+ }
+
+ private ProcessInstanceResult deployAndCreateInstance(
+ final BpmnModelInstance workflow, Map variables) {
+ zeebeClient.newDeployResourceCommand().addProcessModel(workflow, "process.bpmn").send().join();
+
+ return zeebeClient
+ .newCreateInstanceCommand()
+ .bpmnProcessId("process")
+ .latestVersion()
+ .variables(variables)
+ .withResult()
+ .send()
+ .join();
+ }
+}
diff --git a/src/main/java/io/zeebe/script/GraalEvaluator.java b/src/main/java/io/zeebe/script/GraalEvaluator.java
deleted file mode 100644
index 6a9daa2..0000000
--- a/src/main/java/io/zeebe/script/GraalEvaluator.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright © 2017 camunda services GmbH (info@camunda.com)
- *
- * 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 io.zeebe.script;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.LongStream;
-import org.graalvm.polyglot.Context;
-import org.graalvm.polyglot.PolyglotException;
-import org.graalvm.polyglot.Value;
-
-public class GraalEvaluator {
-
- private static final String LANGUAGE_ID_JS = "js";
-
- public static final List SUPPORTED_LANGUAGES = Arrays.asList("js", "javascript");
-
- public Object evaluate(String language, String script, Map variables)
- throws PolyglotException, IllegalArgumentException, IllegalStateException {
-
- // this throws a FileSystemNotFoundException
- final Context context = Context.create(LANGUAGE_ID_JS);
-
- final Value bindings = context.getBindings(LANGUAGE_ID_JS);
- variables.forEach((key, value) -> bindings.putMember(key, value));
-
- final Value result = context.eval(LANGUAGE_ID_JS, script);
-
- return mapValueToObject(result);
- }
-
- private Object mapValueToObject(final Value value) {
- if (value.isNull()) {
- return null;
-
- } else if (value.isBoolean()) {
- return value.asBoolean();
-
- } else if (value.isString()) {
- return value.asString();
-
- } else if (value.isNumber()) {
- if (value.fitsInInt()) {
- return value.asInt();
- } else if (value.fitsInLong()) {
- return value.asLong();
- } else if (value.fitsInFloat()) {
- return value.asFloat();
- } else {
- return value.asDouble();
- }
-
- } else if (value.hasArrayElements()) {
- return LongStream.range(0, value.getArraySize())
- .mapToObj(i -> mapValueToObject(value.getArrayElement(i)))
- .collect(Collectors.toList());
-
- } else if (value.hasMembers()) {
- return value.getMemberKeys().stream()
- .collect(
- Collectors.toMap(Function.identity(), key -> mapValueToObject(value.getMember(key))));
- }
-
- return "unknown: " + value.toString();
- }
-}
diff --git a/src/main/java/io/zeebe/script/MustacheEvaluator.java b/src/main/java/io/zeebe/script/MustacheEvaluator.java
deleted file mode 100644
index 648739e..0000000
--- a/src/main/java/io/zeebe/script/MustacheEvaluator.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.zeebe.script;
-
-import com.samskivert.mustache.Mustache;
-import com.samskivert.mustache.Template;
-import java.util.Map;
-
-public final class MustacheEvaluator implements ZeebeScriptEvaluator {
-
- public Object eval(String script, Map context) {
- final Template template = Mustache.compiler().compile(script);
- return template.execute(context);
- }
-}
diff --git a/src/main/java/io/zeebe/script/ScriptEngineEvaluator.java b/src/main/java/io/zeebe/script/ScriptEngineEvaluator.java
deleted file mode 100644
index 40c22e5..0000000
--- a/src/main/java/io/zeebe/script/ScriptEngineEvaluator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright © 2017 camunda services GmbH (info@camunda.com)
- *
- * 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 io.zeebe.script;
-
-import java.util.HashMap;
-import java.util.Map;
-import javax.script.Bindings;
-import javax.script.ScriptContext;
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
-
-public class ScriptEngineEvaluator {
-
- private final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
-
- private final Map cachedScriptEngines = new HashMap<>();
-
- public Object evaluate(String language, String script, Map variables)
- throws ScriptException {
-
- final ScriptEngine scriptEngine =
- cachedScriptEngines.computeIfAbsent(language, scriptEngineManager::getEngineByName);
-
- if (scriptEngine == null) {
- final String msg = String.format("No script engine found with name '%s'", language);
- throw new RuntimeException(msg);
- }
-
- final ScriptContext context = scriptEngine.getContext();
- final Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
- bindings.putAll(variables);
-
- return scriptEngine.eval(script, context);
- }
-}
diff --git a/src/main/java/io/zeebe/script/ScriptJobHandler.java b/src/main/java/io/zeebe/script/ScriptJobHandler.java
deleted file mode 100644
index 6453e0d..0000000
--- a/src/main/java/io/zeebe/script/ScriptJobHandler.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright © 2017 camunda services GmbH (info@camunda.com)
- *
- * 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 io.zeebe.script;
-
-import io.camunda.zeebe.client.ZeebeClient;
-import io.camunda.zeebe.client.api.response.ActivatedJob;
-import io.camunda.zeebe.client.api.worker.JobClient;
-import io.camunda.zeebe.client.api.worker.JobHandler;
-import io.camunda.zeebe.spring.client.annotation.ZeebeWorker;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Optional;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-@Component
-public class ScriptJobHandler implements JobHandler {
-
- private static final String HEADER_LANGUAGE = "language";
- private static final String HEADER_SCRIPT = "script";
-
- private final ScriptEvaluator scriptEvaluator = new ScriptEvaluator();
-
- private final ZeebeClient zeebeClient;
-
- @Autowired
- public ScriptJobHandler(ZeebeClient zeebeClient) {
- this.zeebeClient = zeebeClient;
- }
-
- @Override
- @ZeebeWorker
- public void handle(JobClient jobClient, ActivatedJob job) {
-
- final Map customHeaders = job.getCustomHeaders();
- final String language = getLanguage(customHeaders);
- final String script = getScript(customHeaders);
-
- final Map variables = job.getVariablesAsMap();
-
- // add context
- variables.put("job", job);
- variables.put("zeebeClient", zeebeClient);
-
- final Object result = scriptEvaluator.evaluate(language, script, variables);
-
- jobClient
- .newCompleteCommand(job.getKey())
- .variables(Collections.singletonMap("result", result))
- .send();
- }
-
- private String getLanguage(Map customHeaders) {
- return Optional.ofNullable(customHeaders.get(HEADER_LANGUAGE))
- .orElseThrow(
- () ->
- new RuntimeException(
- String.format("Missing required custom header '%s'", HEADER_LANGUAGE)));
- }
-
- private String getScript(Map customHeaders) {
- return Optional.ofNullable(customHeaders.get(HEADER_SCRIPT))
- .orElseThrow(
- () ->
- new RuntimeException(
- String.format("Missing required custom header '%s'", HEADER_SCRIPT)));
- }
-}
diff --git a/src/main/java/io/zeebe/script/ZeebeScriptEvaluator.java b/src/main/java/io/zeebe/script/ZeebeScriptEvaluator.java
deleted file mode 100644
index a018863..0000000
--- a/src/main/java/io/zeebe/script/ZeebeScriptEvaluator.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.zeebe.script;
-
-import java.util.Map;
-
-@FunctionalInterface
-public interface ZeebeScriptEvaluator {
-
- Object eval(String script, Map context);
-}
diff --git a/src/main/resources/META-INF/services/org.camunda.feel.valuemapper.CustomValueMapper b/src/main/resources/META-INF/services/org.camunda.feel.valuemapper.CustomValueMapper
deleted file mode 100644
index 406b0de..0000000
--- a/src/main/resources/META-INF/services/org.camunda.feel.valuemapper.CustomValueMapper
+++ /dev/null
@@ -1 +0,0 @@
-org.camunda.feel.impl.JavaValueMapper
\ No newline at end of file
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
deleted file mode 100644
index 30aafd4..0000000
--- a/src/main/resources/application.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-zeebe:
- client:
- worker:
- defaultName: script-worker
- defaultType: script
- threads: 3
-
- job.timeout: 10000
- broker.contactPoint: 127.0.0.1:26500
- security.plaintext: true
-
-logging:
- level:
- root: ERROR
- io.zeebe: INFO
- io.zeebe.script: DEBUG
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
deleted file mode 100644
index d532a9b..0000000
--- a/src/main/resources/banner.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-
-___ __
- _/ _ _ |_ _ (_ _ _ . _ |_ | | _ _ | _ _
-/__ (- (- |_) (- __) (_ | | |_) |_ |/\| (_) | |( (- |
- |
-
-=============================================================
- ${application.formatted-version}