diff --git a/modules/flowable-http-common/src/main/java/org/flowable/http/common/api/HttpRequest.java b/modules/flowable-http-common/src/main/java/org/flowable/http/common/api/HttpRequest.java index 3774e31c0d1..4c600ccb549 100644 --- a/modules/flowable-http-common/src/main/java/org/flowable/http/common/api/HttpRequest.java +++ b/modules/flowable-http-common/src/main/java/org/flowable/http/common/api/HttpRequest.java @@ -37,7 +37,11 @@ public class HttpRequest { protected boolean saveRequest; protected boolean saveResponse; protected boolean saveResponseTransient; + protected boolean saveResponseAsJson; + + protected HttpResponseVariableType responseVariableType; + protected String prefix; public String getMethod() { @@ -165,7 +169,7 @@ public boolean isSaveResponseTransient() { public void setSaveResponseTransient(boolean saveResponseTransient) { this.saveResponseTransient = saveResponseTransient; } - + public boolean isSaveResponseAsJson() { return saveResponseAsJson; } @@ -181,4 +185,12 @@ public String getPrefix() { public void setPrefix(String prefix) { this.prefix = prefix; } + + public HttpResponseVariableType getResponseVariableType() { + return responseVariableType; + } + + public void setResponseVariableType(HttpResponseVariableType responseVariableType) { + this.responseVariableType = responseVariableType; + } } diff --git a/modules/flowable-http-common/src/main/java/org/flowable/http/common/api/HttpResponseVariableType.java b/modules/flowable-http-common/src/main/java/org/flowable/http/common/api/HttpResponseVariableType.java new file mode 100644 index 00000000000..708c9558553 --- /dev/null +++ b/modules/flowable-http-common/src/main/java/org/flowable/http/common/api/HttpResponseVariableType.java @@ -0,0 +1,21 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.http.common.api; + +public enum HttpResponseVariableType { + + AUTO, + STRING, + BASE64, + BYTES +} diff --git a/modules/flowable-http-common/src/main/java/org/flowable/http/common/impl/BaseHttpActivityDelegate.java b/modules/flowable-http-common/src/main/java/org/flowable/http/common/impl/BaseHttpActivityDelegate.java index cbdeea2ca8d..2dad943e39d 100644 --- a/modules/flowable-http-common/src/main/java/org/flowable/http/common/impl/BaseHttpActivityDelegate.java +++ b/modules/flowable-http-common/src/main/java/org/flowable/http/common/impl/BaseHttpActivityDelegate.java @@ -13,6 +13,11 @@ package org.flowable.http.common.impl; import java.io.IOException; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -25,12 +30,15 @@ import org.flowable.http.common.api.HttpHeaders; import org.flowable.http.common.api.HttpRequest; import org.flowable.http.common.api.HttpResponse; +import org.flowable.http.common.api.HttpResponseVariableType; import org.flowable.http.common.api.client.AsyncExecutableHttpRequest; import org.flowable.http.common.api.client.ExecutableHttpRequest; import org.flowable.http.common.api.client.FlowableHttpClient; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.MissingNode; +import com.fasterxml.jackson.databind.node.NullNode; /** * @author Filip Hrisafov @@ -44,6 +52,29 @@ public abstract class BaseHttpActivityDelegate { public static final String HTTP_TASK_REQUEST_HEADERS_INVALID = "requestHeaders are invalid"; public static final String HTTP_TASK_REQUEST_FIELD_INVALID = "request fields are invalid"; + /* These are interpreted as binary types and base64 encoded by default. */ + protected static final Set DEFAULT_BINARY_CONTENT_TYPES = new HashSet<>( + Arrays.asList("application/octet-stream", + "application/pdf", + "image/gif", + "image/jpeg", + "image/png", + "image/svg+xml", + "image/tiff", + "audio/mpeg", + "audio/wav", + "video/mpeg", + "video/mp4", + "application/zip", + "application/rtf", + "application/msword", + "application/vnd.ms-excel", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.visio")); + // HttpRequest method (GET,POST,PUT etc) protected Expression requestMethod; // HttpRequest URL (http://flowable.org) @@ -74,6 +105,8 @@ public abstract class BaseHttpActivityDelegate { protected Expression saveResponseParametersTransient; // Flag to save the response variable as an ObjectNode instead of a String protected Expression saveResponseVariableAsJson; + + protected Expression responseVariableType; // Prefix for the execution variable names (Optional) protected Expression resultVariablePrefix; @@ -103,7 +136,19 @@ protected HttpRequest createRequest(VariableContainer variableContainer, String request.setSaveRequest(ExpressionUtils.getBooleanFromField(saveRequestVariables, variableContainer)); request.setSaveResponse(ExpressionUtils.getBooleanFromField(saveResponseParameters, variableContainer)); request.setSaveResponseTransient(ExpressionUtils.getBooleanFromField(saveResponseParametersTransient, variableContainer)); - request.setSaveResponseAsJson(ExpressionUtils.getBooleanFromField(saveResponseVariableAsJson, variableContainer)); + boolean saveResponseAsJson = ExpressionUtils.getBooleanFromField(saveResponseVariableAsJson, variableContainer); + request.setSaveResponseAsJson(saveResponseAsJson); + + if (responseVariableType != null) { + String responseVariableTypeString = ExpressionUtils.getStringFromField(responseVariableType, variableContainer).toUpperCase(Locale.ROOT); + HttpResponseVariableType responseVariableType = HttpResponseVariableType.valueOf(responseVariableTypeString); + if (responseVariableType != null && saveResponseAsJson) { + throw new FlowableIllegalArgumentException( + "Cannot set both fields 'saveResponseVariableAsJson' and 'responseVariableType'. Please set either of these."); + } + request.setResponseVariableType(responseVariableType); + } + request.setPrefix(ExpressionUtils.getStringFromField(resultVariablePrefix, variableContainer)); String failCodes = ExpressionUtils.getStringFromField(failStatusCodes, variableContainer); @@ -160,9 +205,22 @@ protected void saveResponseFields(VariableContainer variableContainer, HttpReque if (!response.isBodyResponseHandled()) { String responseVariableName = ExpressionUtils.getStringFromField(this.responseVariableName, variableContainer); String varName = StringUtils.isNotEmpty(responseVariableName) ? responseVariableName : request.getPrefix() + "ResponseBody"; - Object varValue = request.isSaveResponseAsJson() && response.getBody() != null ? objectMapper.readTree(response.getBody()) : response.getBody(); - if (varValue instanceof MissingNode) { - varValue = null; + + HttpResponseVariableType responseVariableType = request.getResponseVariableType(); + Object varValue; + if (responseVariableType != null) { + // explicit response variable type + varValue = determineResponseVariableValue(request, response, objectMapper); + } else { + // without explicit response variable type + if (request.isSaveResponseAsJson() && response.getBody() != null) { + varValue = objectMapper.readTree(response.getBody()); + } else { + varValue = response.getBody(); + } + if (varValue instanceof MissingNode) { + varValue = null; + } } if (request.isSaveResponseTransient()) { variableContainer.setTransientVariable(varName, varValue); @@ -202,6 +260,71 @@ protected void saveResponseFields(VariableContainer variableContainer, HttpReque } } + protected Object determineResponseVariableValue(HttpRequest request, HttpResponse response, ObjectMapper objectMapper) throws IOException { + HttpResponseVariableType responseVariableType = request.getResponseVariableType(); + switch (responseVariableType) { + case AUTO: + // default is best guess based on content type + if (isKnownBinaryContentType(response)) { + return response.getBodyBytes(); + } else if (isJsonContentType(response)) { + return readJsonTree(response, objectMapper); + } else { + return response.getBody(); + } + case STRING: { + return response.getBody(); + } + case BYTES: { + return response.getBodyBytes(); + } + case BASE64: { + byte[] bodyBytes = response.getBodyBytes(); + return Base64.getEncoder().encodeToString(bodyBytes); + } + default: { + throw new FlowableException("Unsupported response variable type: " + responseVariableType); + } + } + } + + protected Object readJsonTree(HttpResponse response, ObjectMapper objectMapper) throws JsonProcessingException { + Object varValue; + if (response.getBody() != null) { + varValue = objectMapper.readTree(response.getBody()); + } else { + varValue = response.getBody(); + } + if (varValue instanceof MissingNode || varValue instanceof NullNode) { + varValue = null; + } + return varValue; + } + + protected boolean isKnownBinaryContentType(HttpResponse response) { + List contentTypeHeader = response.getHttpHeaders().get("Content-Type"); + if (contentTypeHeader != null && !contentTypeHeader.isEmpty()) { + String contentType = contentTypeHeader.get(0); + if (contentType.indexOf(';') >= 0) { + contentType = contentType.split(";")[0]; // remove charset if present + } + return DEFAULT_BINARY_CONTENT_TYPES.contains(contentType); + } + return false; + } + + protected boolean isJsonContentType(HttpResponse response) { + List contentTypeHeader = response.getHttpHeaders().get("Content-Type"); + if (contentTypeHeader != null && !contentTypeHeader.isEmpty()) { + String contentType = contentTypeHeader.get(0); + if (contentType.indexOf(';') >= 0) { + contentType = contentType.split(";")[0]; // remove charset if present + } + return contentType != null && contentType.endsWith("/json") || contentType.endsWith("+json"); + } + return false; + } + protected CompletableFuture prepareAndExecuteRequest(HttpRequest request, boolean parallelInSameTransaction, AsyncTaskInvoker taskInvoker) { ExecutableHttpRequest httpRequest = httpClient.prepareRequest(request); diff --git a/modules/flowable-http/src/main/java/org/flowable/http/HttpRequest.java b/modules/flowable-http/src/main/java/org/flowable/http/HttpRequest.java index 066efe8fd03..1b136e8ec33 100644 --- a/modules/flowable-http/src/main/java/org/flowable/http/HttpRequest.java +++ b/modules/flowable-http/src/main/java/org/flowable/http/HttpRequest.java @@ -17,6 +17,7 @@ import org.flowable.http.common.api.HttpHeaders; import org.flowable.http.common.api.MultiValuePart; +import org.flowable.http.common.api.HttpResponseVariableType; /** * @author Harsha Teja Kanna. @@ -272,6 +273,20 @@ public void setSaveResponseAsJson(boolean saveResponseAsJson) { } } + @Override + public HttpResponseVariableType getResponseVariableType() { + return delegate != null ? delegate.getResponseVariableType() : super.getResponseVariableType(); + } + + @Override + public void setResponseVariableType(HttpResponseVariableType responseVariableType) { + if (delegate != null) { + delegate.setResponseVariableType(responseVariableType); + } else { + super.setResponseVariableType(responseVariableType); + } + } + @Override public String getPrefix() { return delegate != null ? delegate.getPrefix() : super.getPrefix(); diff --git a/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTest.java b/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTest.java index 69911ff0bb3..e98fa2e46f1 100644 --- a/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTest.java +++ b/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTest.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -51,6 +52,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import net.javacrumbs.jsonunit.core.Option; @@ -675,6 +677,124 @@ public void testGetWithVariableParameters(String requestParam, String expectedRe assertProcessEnded(procId); } + @Test + @Deployment(resources = "org/flowable/http/bpmn/HttpServiceTaskTest.testGetWithSaveResponseVariableAndVariableType.bpmn20.xml") + public void testGetWithSaveResponseVariablePdfIsPutAsByteArrayByDefault() { + String procId = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("simpleGetOnly") + .transientVariable("requestUrl","http://localhost:9798/binary/pdf") + .transientVariable("responseVariableType","auto") + .start() + .getId(); + List variables = historyService.createHistoricVariableInstanceQuery().processInstanceId(procId).list(); + assertThat(variables) + .extracting(HistoricVariableInstance::getVariableName) + .containsExactly("responseVariable"); + assertThat(variables.get(0).getValue()).isInstanceOfSatisfying(byte[].class, value -> assertThat(value).isNotEmpty()); + assertProcessEnded(procId); + + + // request byte array + procId = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("simpleGetOnly") + .transientVariable("requestUrl","http://localhost:9798/binary/pdf") + .transientVariable("responseVariableType","bytes") + .start() + .getId(); + variables = historyService.createHistoricVariableInstanceQuery().processInstanceId(procId).list(); + assertThat(variables) + .extracting(HistoricVariableInstance::getVariableName) + .containsExactly("responseVariable"); + assertThat(variables.get(0).getValue()).isInstanceOfSatisfying(byte[].class, value -> assertThat(value).isNotEmpty()); + assertProcessEnded(procId); + } + + /* + * When setting string as response variable type, the response should not be base64 encoded + */ + @Test + @Deployment(resources = "org/flowable/http/bpmn/HttpServiceTaskTest.testGetWithSaveResponseVariableAndVariableType.bpmn20.xml") + public void testGetWithSaveResponseVariableAndVariableTypeStringOctetStream() { + String procId = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("simpleGetOnly") + .transientVariable("requestUrl","http://localhost:9798/binary/octet-stream-string") + .transientVariable("responseVariableType","string") + .start() + .getId(); + List variables = historyService.createHistoricVariableInstanceQuery().processInstanceId(procId).list(); + assertThat(variables) + .extracting(HistoricVariableInstance::getVariableName) + .containsExactly("responseVariable"); + assertThat(variables.get(0).getValue()).isInstanceOfSatisfying(String.class, + value -> assertThat(value).isEqualTo("Content-Type is octet-stream, but still a string")); + assertProcessEnded(procId); + } + + @Test + @Deployment(resources = "org/flowable/http/bpmn/HttpServiceTaskTest.testGetWithSaveResponseVariableAndVariableType.bpmn20.xml") + public void testGetWithSaveResponseVariableAndVariableTypeJsonContent() { + // DEFAULT + String procId = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("simpleGetOnly") + .transientVariable("requestUrl","http://localhost:9798/test") + .transientVariable("responseVariableType","auto") + .start() + .getId(); + List variables = historyService.createHistoricVariableInstanceQuery().processInstanceId(procId).list(); + assertThat(variables) + .extracting(HistoricVariableInstance::getVariableName) + .containsExactly("responseVariable"); + assertThat(variables.get(0).getValue()).isInstanceOfSatisfying(ObjectNode.class, + value -> assertThatJson(value).isEqualTo("{name:{firstName:'John', lastName:'Doe'}}")); + assertProcessEnded(procId); + + // BASE64 + procId = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("simpleGetOnly") + .transientVariable("requestUrl","http://localhost:9798/test") + .transientVariable("responseVariableType","base64") + .start() + .getId(); + variables = historyService.createHistoricVariableInstanceQuery().processInstanceId(procId).list(); + assertThat(variables) + .extracting(HistoricVariableInstance::getVariableName) + .containsExactly("responseVariable"); + assertThat(variables.get(0).getValue()).isInstanceOfSatisfying(String.class, + value -> assertThat(value).isEqualTo("eyJuYW1lIjp7ImZpcnN0TmFtZSI6IkpvaG4iLCJsYXN0TmFtZSI6IkRvZSJ9fQo=")); + assertProcessEnded(procId); + + // BYTES + procId = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("simpleGetOnly") + .transientVariable("requestUrl","http://localhost:9798/test") + .transientVariable("responseVariableType","bytes") + .start() + .getId(); + variables = historyService.createHistoricVariableInstanceQuery().processInstanceId(procId).list(); + assertThat(variables) + .extracting(HistoricVariableInstance::getVariableName) + .containsExactly("responseVariable"); + assertThat(variables.get(0).getValue()).isInstanceOfSatisfying(byte[].class, + value -> assertThat(value).asString(StandardCharsets.UTF_8).isEqualToIgnoringWhitespace("{\"name\":{\"firstName\":\"John\",\"lastName\":\"Doe\"}}")); + assertProcessEnded(procId); + + // STRING + procId = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("simpleGetOnly") + .transientVariable("requestUrl","http://localhost:9798/test") + .transientVariable("responseVariableType","string") + .start() + .getId(); + variables = historyService.createHistoricVariableInstanceQuery().processInstanceId(procId).list(); + assertThat(variables) + .extracting(HistoricVariableInstance::getVariableName) + .containsExactly("responseVariable"); + assertThat(variables.get(0).getValue()).isInstanceOfSatisfying(String.class, + value -> assertThat(value).isEqualToIgnoringWhitespace("{\"name\":{\"firstName\":\"John\",\"lastName\":\"Doe\"}}")); + assertProcessEnded(procId); + } + + static Stream parametersForGetWithVariableParameters() { return Stream.of( Arguments.arguments("Test+ Plus", "Test+ Plus"), diff --git a/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTestServer.java b/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTestServer.java index 4f10e312d80..106756fd9b4 100644 --- a/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTestServer.java +++ b/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTestServer.java @@ -95,6 +95,8 @@ public class HttpServiceTaskTestServer { httpServiceTaskServletHolder.getRegistration().setMultipartConfig(multipartConfig); contextHandler.addServlet(httpServiceTaskServletHolder, "/api/*"); contextHandler.addServlet(new ServletHolder(new SimpleHttpServiceTaskTestServlet()), "/test"); + contextHandler.addServlet(new ServletHolder(new SimpleHttpServiceTaskBinaryContentTestServlet()), "/binary/pdf"); + contextHandler.addServlet(new ServletHolder(new OctetStreamStringTestServlet()), "/binary/octet-stream-string"); contextHandler.addServlet(new ServletHolder(new HelloServlet()), "/hello"); contextHandler.addServlet(new ServletHolder(new ArrayResponseServlet()), "/array-response"); contextHandler.addServlet(new ServletHolder(new DeleteResponseServlet()), "/delete"); @@ -286,6 +288,31 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se resp.getWriter().println(responseNode); } } + + private static class OctetStreamStringTestServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setStatus(200); + resp.setContentType("application/octet-stream"); + resp.getWriter().write("Content-Type is octet-stream, but still a string"); + } + } + + private static class SimpleHttpServiceTaskBinaryContentTestServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setStatus(200); + resp.setContentType("application/pdf"); + InputStream byteArrayInputStream = getClass().getClassLoader().getResourceAsStream("org/flowable/http/content/sample.pdf"); + IOUtils.copy(byteArrayInputStream, resp.getOutputStream()); + } + } private static class HelloServlet extends HttpServlet { diff --git a/modules/flowable-http/src/test/java/org/flowable/http/cmmn/CmmnHttpTaskTest.java b/modules/flowable-http/src/test/java/org/flowable/http/cmmn/CmmnHttpTaskTest.java index b2fecb2d0b9..489f8266383 100644 --- a/modules/flowable-http/src/test/java/org/flowable/http/cmmn/CmmnHttpTaskTest.java +++ b/modules/flowable-http/src/test/java/org/flowable/http/cmmn/CmmnHttpTaskTest.java @@ -12,11 +12,13 @@ */ package org.flowable.http.cmmn; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -29,6 +31,8 @@ import org.junit.Rule; import org.junit.Test; +import com.fasterxml.jackson.databind.node.ObjectNode; + /** * @author martin.grofcik */ @@ -319,6 +323,103 @@ public void testExpressions() throws Exception { ); } + @Test + @CmmnDeployment(resources = "org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithResponseVariableAndVariableType.cmmn") + public void testGetWithSaveResponseVariablePdfIsPutAsByteArrayByDefault() { + // default + CaseInstance caseInstance = cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + .caseDefinitionKey("myCase") + .transientVariable("responseVariableType","auto") + .transientVariable("requestUrl","http://localhost:9798/binary/pdf") + .start(); + + Map variables = caseInstance.getCaseVariables(); + assertThat(variables.get("responseVariable")).isInstanceOfSatisfying(byte[].class, value -> assertThat(value).isNotEmpty()); + + // request byte array + caseInstance = cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + .caseDefinitionKey("myCase") + .transientVariable("responseVariableType","bytes") + .transientVariable("requestUrl","http://localhost:9798/binary/pdf") + .start(); + + variables = caseInstance.getCaseVariables(); + assertThat(variables.get("responseVariable")).isInstanceOfSatisfying(byte[].class, value -> assertThat(value).isNotEmpty()); + } + + @Test + @CmmnDeployment(resources = "org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithResponseVariableAndVariableType.cmmn") + public void testGetWithSaveResponseVariableAndVariableTypeStringOctetStream() { + CaseInstance caseInstance = cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + .caseDefinitionKey("myCase") + .transientVariable("responseVariableType","string") + .transientVariable("requestUrl","http://localhost:9798/binary/octet-stream-string") + .start(); + + Map variables = caseInstance.getCaseVariables(); + assertThat(variables.get("responseVariable")).isInstanceOfSatisfying(String.class, + value -> assertThat(value).isEqualTo("Content-Type is octet-stream, but still a string")); + } + + @Test + @CmmnDeployment(resources = "org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithResponseVariableAndVariableType.cmmn") + public void testGetWithSaveResponseVariableAndVariableTypeJsonContent() { + // DEFAULT + CaseInstance caseInstance = cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + .caseDefinitionKey("myCase") + .transientVariable("responseVariableType", "auto") + .transientVariable("requestUrl", "http://localhost:9798/test") + .start(); + + Map variables = caseInstance.getCaseVariables(); + assertThat(variables.get("responseVariable")).isInstanceOfSatisfying(ObjectNode.class, + value -> assertThatJson(value).isEqualTo("{name:{firstName:'John', lastName:'Doe'}}")); + + // BASE64 + caseInstance = cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + .caseDefinitionKey("myCase") + .transientVariable("responseVariableType", "base64") + .transientVariable("requestUrl", "http://localhost:9798/test") + .start(); + + variables = caseInstance.getCaseVariables(); + assertThat(variables.get("responseVariable")).isInstanceOfSatisfying(String.class, + value -> assertThat(value).isEqualTo("eyJuYW1lIjp7ImZpcnN0TmFtZSI6IkpvaG4iLCJsYXN0TmFtZSI6IkRvZSJ9fQo=")); + + // BYTES + caseInstance = cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + .caseDefinitionKey("myCase") + .transientVariable("responseVariableType", "bytes") + .transientVariable("requestUrl", "http://localhost:9798/test") + .start(); + + variables = caseInstance.getCaseVariables(); + assertThat(variables.get("responseVariable")).isInstanceOfSatisfying(byte[].class, + value -> assertThat(value).asString(StandardCharsets.UTF_8) + .isEqualToIgnoringWhitespace("{\"name\":{\"firstName\":\"John\",\"lastName\":\"Doe\"}}")); + + // STRING + caseInstance = cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + .caseDefinitionKey("myCase") + .transientVariable("responseVariableType", "string") + .transientVariable("requestUrl", "http://localhost:9798/test") + .start(); + + variables = caseInstance.getCaseVariables(); + assertThat(variables.get("responseVariable")).isInstanceOfSatisfying(String.class, + value -> assertThatJson(value).isEqualTo("{\"name\":{\"firstName\":\"John\",\"lastName\":\"Doe\"}}")); + } + + @Test + @CmmnDeployment(resources = "org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithSaveResponseVariableAndVariableType.cmmn") + public void testGetWithInvalidConfigJsonAndVariableResponseType() { + assertThatThrownBy(() -> cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + .caseDefinitionKey("myCase") + .transientVariable("requestUrl", "http://localhost:9798/binary/pdf") + .start()).isInstanceOf(FlowableException.class) + .hasMessage("Cannot set both fields 'saveResponseVariableAsJson' and 'responseVariableType'. Please set either of these."); + } + protected CaseInstance createCaseInstance() { return cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() .caseDefinitionKey("myCase") diff --git a/modules/flowable-http/src/test/resources/org/flowable/http/bpmn/HttpServiceTaskTest.testGetWithSaveResponseVariableAndVariableType.bpmn20.xml b/modules/flowable-http/src/test/resources/org/flowable/http/bpmn/HttpServiceTaskTest.testGetWithSaveResponseVariableAndVariableType.bpmn20.xml new file mode 100644 index 00000000000..9f103d4c667 --- /dev/null +++ b/modules/flowable-http/src/test/resources/org/flowable/http/bpmn/HttpServiceTaskTest.testGetWithSaveResponseVariableAndVariableType.bpmn20.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-http/src/test/resources/org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithResponseVariableAndVariableType.cmmn b/modules/flowable-http/src/test/resources/org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithResponseVariableAndVariableType.cmmn new file mode 100644 index 00000000000..1c5e7debc4c --- /dev/null +++ b/modules/flowable-http/src/test/resources/org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithResponseVariableAndVariableType.cmmn @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + complete + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + diff --git a/modules/flowable-http/src/test/resources/org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithSaveResponseVariableAndVariableType.cmmn b/modules/flowable-http/src/test/resources/org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithSaveResponseVariableAndVariableType.cmmn new file mode 100644 index 00000000000..5d34725daa7 --- /dev/null +++ b/modules/flowable-http/src/test/resources/org/flowable/http/cmmn/CmmnHttpTaskTest.testGetWithSaveResponseVariableAndVariableType.cmmn @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + complete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-http/src/test/resources/org/flowable/http/content/sample.pdf b/modules/flowable-http/src/test/resources/org/flowable/http/content/sample.pdf new file mode 100644 index 00000000000..774c2ea70c5 Binary files /dev/null and b/modules/flowable-http/src/test/resources/org/flowable/http/content/sample.pdf differ