diff --git a/pom.xml b/pom.xml index 56babee4..43ea2fe7 100644 --- a/pom.xml +++ b/pom.xml @@ -245,7 +245,37 @@ org.graalvm.js js - 23.0.3 + 23.0.4 + + + + org.graalvm.python + python-language + 23.1.3 + + + + org.graalvm.python + python-resources + 23.1.3 + + + + org.graalvm.ruby + ruby-language + 23.1.3 + + + + org.graalvm.ruby + ruby-resources + 23.1.3 + + + + org.jruby + jruby + 9.4.7.0 @@ -254,6 +284,12 @@ 23.1.2 + + org.graalvm.polyglot + polyglot + 23.1.3 + + org.springframework.boot spring-boot-starter-actuator diff --git a/src/main/java/org/folio/rest/camunda/controller/WorkflowController.java b/src/main/java/org/folio/rest/camunda/controller/WorkflowController.java index b740e6ef..e54d5df3 100644 --- a/src/main/java/org/folio/rest/camunda/controller/WorkflowController.java +++ b/src/main/java/org/folio/rest/camunda/controller/WorkflowController.java @@ -1,6 +1,7 @@ package org.folio.rest.camunda.controller; import lombok.extern.slf4j.Slf4j; +import org.folio.rest.camunda.exception.ScriptTaskDeserializeCodeFailure; import org.folio.rest.camunda.exception.WorkflowAlreadyActiveException; import org.folio.rest.camunda.service.CamundaApiService; import org.folio.rest.workflow.model.Workflow; @@ -26,7 +27,7 @@ public WorkflowController(CamundaApiService camundaApiService) { @PostMapping(value = {"/activate", "/activate/"}, produces = { MediaType.APPLICATION_JSON_VALUE }) public Workflow activateWorkflow(@RequestBody Workflow workflow, @TenantHeader String tenant) - throws WorkflowAlreadyActiveException { + throws WorkflowAlreadyActiveException, ScriptTaskDeserializeCodeFailure { log.debug("Activating Workflow: {}", workflow == null ? null : workflow.getId()); return camundaApiService.deployWorkflow(workflow, tenant); } diff --git a/src/main/java/org/folio/rest/camunda/exception/ScriptTaskDeserializeCodeFailure.java b/src/main/java/org/folio/rest/camunda/exception/ScriptTaskDeserializeCodeFailure.java new file mode 100644 index 00000000..33fe1d24 --- /dev/null +++ b/src/main/java/org/folio/rest/camunda/exception/ScriptTaskDeserializeCodeFailure.java @@ -0,0 +1,17 @@ +package org.folio.rest.camunda.exception; + +public class ScriptTaskDeserializeCodeFailure extends Exception { + + private static final long serialVersionUID = -6270663785866339965L; + + private static final String MESSAGE = "Failed to De-serialize code for ScriptTask %s."; + + public ScriptTaskDeserializeCodeFailure(String scriptTaskUuid) { + super(String.format(MESSAGE, scriptTaskUuid)); + } + + public ScriptTaskDeserializeCodeFailure(String scriptTaskUuid, Exception e) { + super(String.format(MESSAGE, scriptTaskUuid), e); + } + +} diff --git a/src/main/java/org/folio/rest/camunda/service/BpmnModelFactory.java b/src/main/java/org/folio/rest/camunda/service/BpmnModelFactory.java index 5c749b09..f87ec615 100644 --- a/src/main/java/org/folio/rest/camunda/service/BpmnModelFactory.java +++ b/src/main/java/org/folio/rest/camunda/service/BpmnModelFactory.java @@ -23,6 +23,7 @@ import org.camunda.bpm.model.bpmn.instance.camunda.CamundaField; import org.camunda.bpm.model.xml.instance.ModelElementInstance; import org.folio.rest.camunda.delegate.AbstractWorkflowDelegate; +import org.folio.rest.camunda.exception.ScriptTaskDeserializeCodeFailure; import org.folio.rest.workflow.enums.StartEventType; import org.folio.rest.workflow.model.Condition; import org.folio.rest.workflow.model.ConnectTo; @@ -77,7 +78,7 @@ public class BpmnModelFactory { @Autowired private List workflowDelegates; - public BpmnModelInstance fromWorkflow(Workflow workflow) { + public BpmnModelInstance fromWorkflow(Workflow workflow) throws ScriptTaskDeserializeCodeFailure { // @formatter:off ProcessBuilder processBuilder = Bpmn.createExecutableProcess().name(workflow.getName()) @@ -90,7 +91,13 @@ public BpmnModelInstance fromWorkflow(Workflow workflow) { // @formatter:off workflow.getNodes().stream() .filter(node -> node instanceof EventSubprocess) - .forEach(subprocess -> eventSubprocess(processBuilder, subprocess)); + .forEach(subprocess -> { + try { + eventSubprocess(processBuilder, subprocess); + } catch (ScriptTaskDeserializeCodeFailure e) { + throw new RuntimeException(e); + } + }); // @formatter:on setup(model, workflow); @@ -98,7 +105,7 @@ public BpmnModelInstance fromWorkflow(Workflow workflow) { return model; } - private BpmnModelInstance build(ProcessBuilder processBuilder, Workflow workflow) { + private BpmnModelInstance build(ProcessBuilder processBuilder, Workflow workflow) throws ScriptTaskDeserializeCodeFailure { List nodes = workflow.getNodes(); if (nodes.isEmpty()) { @@ -117,7 +124,7 @@ private BpmnModelInstance build(ProcessBuilder processBuilder, Workflow workflow return builder.done(); } - private void eventSubprocess(ProcessBuilder processBuilder, Node node) { + private void eventSubprocess(ProcessBuilder processBuilder, Node node) throws ScriptTaskDeserializeCodeFailure { AbstractFlowNodeBuilder builder = null; String identifier = node.getIdentifier(); String name = node.getName(); @@ -125,7 +132,7 @@ private void eventSubprocess(ProcessBuilder processBuilder, Node node) { builder = build(builder, ((EventSubprocess) node).getNodes(), Setup.NONE); } - private AbstractFlowNodeBuilder build(AbstractFlowNodeBuilder builder, List nodes, Setup setup) { + private AbstractFlowNodeBuilder build(AbstractFlowNodeBuilder builder, List nodes, Setup setup) throws ScriptTaskDeserializeCodeFailure { for (Node node : nodes) { @@ -314,7 +321,6 @@ private void eventSubprocess(ProcessBuilder processBuilder, Node node) { } else { logger.warn("Navigation named {} is of an unknown type.", node.getName()); } - } else if (node instanceof Task) { if (node instanceof Wait) { if (node instanceof ReceiveTask) { @@ -324,13 +330,19 @@ private void eventSubprocess(ProcessBuilder processBuilder, Node node) { logger.warn("Wait Task named {} is of an unknown type.", node.getName()); } } else if (node instanceof ScriptTask) { + String code; + try { + code = objectMapper.readValue(((ScriptTask) node).getCode(), String.class); + } catch (JsonProcessingException e) { + throw new ScriptTaskDeserializeCodeFailure(node.getId(), e); + } + builder = builder.scriptTask(node.getIdentifier()).name(node.getName()) - .scriptFormat(((ScriptTask) node).getScriptFormat()).scriptText(((ScriptTask) node).getCode()); + .scriptFormat(((ScriptTask) node).getScriptFormat()).scriptText(code); if (((ScriptTask) node).hasResultVariable()) { builder = ((ScriptTaskBuilder) builder).camundaResultVariable(((ScriptTask) node).getResultVariable()); } - } else { logger.warn("Script Task named {} is of an unknown type.", node.getName()); } diff --git a/src/main/java/org/folio/rest/camunda/service/CamundaApiService.java b/src/main/java/org/folio/rest/camunda/service/CamundaApiService.java index 1e52d25b..d10afd4a 100644 --- a/src/main/java/org/folio/rest/camunda/service/CamundaApiService.java +++ b/src/main/java/org/folio/rest/camunda/service/CamundaApiService.java @@ -6,6 +6,7 @@ import org.camunda.bpm.engine.repository.Deployment; import org.camunda.bpm.model.bpmn.Bpmn; import org.camunda.bpm.model.bpmn.BpmnModelInstance; +import org.folio.rest.camunda.exception.ScriptTaskDeserializeCodeFailure; import org.folio.rest.camunda.exception.WorkflowAlreadyActiveException; import org.folio.rest.workflow.model.Workflow; import org.springframework.beans.factory.annotation.Autowired; @@ -17,7 +18,7 @@ public class CamundaApiService { @Autowired private BpmnModelFactory bpmnModelFactory; - public Workflow deployWorkflow(Workflow workflow, String tenant) throws WorkflowAlreadyActiveException { + public Workflow deployWorkflow(Workflow workflow, String tenant) throws WorkflowAlreadyActiveException, ScriptTaskDeserializeCodeFailure { if (workflow.isActive()) { throw new WorkflowAlreadyActiveException(workflow.getId()); } diff --git a/src/main/java/org/folio/rest/camunda/utility/ScriptEngineUtility.java b/src/main/java/org/folio/rest/camunda/utility/ScriptEngineUtility.java new file mode 100644 index 00000000..8314d97e --- /dev/null +++ b/src/main/java/org/folio/rest/camunda/utility/ScriptEngineUtility.java @@ -0,0 +1,37 @@ +package org.folio.rest.camunda.utility; + +import org.graalvm.shadowed.org.json.JSONObject; + +/** + * Provide utility functions specifically needed for scripting engines. + */ +public class ScriptEngineUtility { + + /** + * Decode a JSON string into a JSONObject. + * + * This is required by several of the scripting engines, such as engine.py. + * + * @param json + * The JSON string to decode. + * + * @return + * A generated JSON object, containing the decoded JSON string. + */ + public JSONObject decodeJson(String json) { + return new JSONObject(json); + } + + /** + * Encode a JSONObject into a JSON string. + * + * @param json + * The JSONObject to encode. + * + * @return + * A String containing the encoded JSON data. + */ + public String encodeJson(JSONObject json) { + return json.toString(2); + } +} diff --git a/src/main/resources/scripts/engine.groovy b/src/main/resources/scripts/engine.groovy index 54a239a0..2bf87c60 100644 --- a/src/main/resources/scripts/engine.groovy +++ b/src/main/resources/scripts/engine.groovy @@ -1,4 +1,4 @@ -import org.folio.rest.utility.ScriptEngineUtility +import org.folio.rest.camunda.utility.ScriptEngineUtility def %s(String inArgs) { def scriptEngineUtility = new ScriptEngineUtility(); diff --git a/src/main/resources/scripts/engine.java b/src/main/resources/scripts/engine.java index ed64bb91..10485991 100644 --- a/src/main/resources/scripts/engine.java +++ b/src/main/resources/scripts/engine.java @@ -1,5 +1,5 @@ -import org.camunda.bpm.engine.impl.util.json.JSONObject; -import org.folio.rest.utility.ScriptEngineUtility; +import org.graalvm.shadowed.org.json.JSONObject; +import org.folio.rest.camunda.utility.ScriptEngineUtility; public String %s(String inArgs) { ScriptEngineUtility scriptEngineUtility = new ScriptEngineUtility(); diff --git a/src/main/resources/scripts/engine.pl b/src/main/resources/scripts/engine.pl index 10572f23..3681e14b 100644 --- a/src/main/resources/scripts/engine.pl +++ b/src/main/resources/scripts/engine.pl @@ -1,5 +1,5 @@ use JSON; -use org.folio.rest.utility.ScriptEngineUtility; +use org.folio.rest.camunda.utility.ScriptEngineUtility; sub %s($) { my ($inArgs) = @_; diff --git a/src/main/resources/scripts/engine.py b/src/main/resources/scripts/engine.py index 3aa9359b..e541fefe 100644 --- a/src/main/resources/scripts/engine.py +++ b/src/main/resources/scripts/engine.py @@ -1,8 +1,8 @@ import json -import org.folio.rest.utility.ScriptEngineUtility; +import org.folio.rest.camunda.utility.ScriptEngineUtility; def %s(inArgs): - scriptEngineUtility = org.folio.rest.utility.ScriptEngineUtility(); + scriptEngineUtility = org.folio.rest.camunda.utility.ScriptEngineUtility(); args = scriptEngineUtility.decodeJson(inArgs); returnObj = scriptEngineUtility.createJson(); %s diff --git a/src/main/resources/scripts/engine.rb b/src/main/resources/scripts/engine.rb index 86380595..4e4c29b1 100644 --- a/src/main/resources/scripts/engine.rb +++ b/src/main/resources/scripts/engine.rb @@ -1,5 +1,5 @@ require 'json' -require 'use org.folio.rest.utility.ScriptEngineUtility' +require 'use org.folio.rest.camunda.utility.ScriptEngineUtility' def %s(inArgs) scriptEngineUtility = ScriptEngineUtility(); diff --git a/src/test/java/org/folio/rest/camunda/exception/ScriptTaskDeserializeCodeFailureTest.java b/src/test/java/org/folio/rest/camunda/exception/ScriptTaskDeserializeCodeFailureTest.java new file mode 100644 index 00000000..007b06d7 --- /dev/null +++ b/src/test/java/org/folio/rest/camunda/exception/ScriptTaskDeserializeCodeFailureTest.java @@ -0,0 +1,35 @@ +package org.folio.rest.camunda.exception; + +import static org.folio.spring.test.mock.MockMvcConstant.UUID; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ScriptTaskDeserializeCodeFailureTest { + + @Test + void scriptEngineLoadFailedWorksTest() throws IOException { + ScriptTaskDeserializeCodeFailure exception = Assertions.assertThrows(ScriptTaskDeserializeCodeFailure.class, () -> { + throw new ScriptTaskDeserializeCodeFailure(UUID); + }); + + assertNotNull(exception); + assertTrue(exception.getMessage().contains(UUID)); + } + + @Test + void scriptEngineLoadFailedWorksWithParameterTest() throws IOException { + ScriptTaskDeserializeCodeFailure exception = Assertions.assertThrows(ScriptTaskDeserializeCodeFailure.class, () -> { + throw new ScriptTaskDeserializeCodeFailure(UUID, new RuntimeException()); + }); + + assertNotNull(exception); + assertTrue(exception.getMessage().contains(UUID)); + } +}