diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/CmmnRestUrls.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/CmmnRestUrls.java
index bf3f8f84edf..081f5bdccc2 100644
--- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/CmmnRestUrls.java
+++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/CmmnRestUrls.java
@@ -40,6 +40,7 @@ public final class CmmnRestUrls {
public static final String SEGMENT_CASE_INSTANCE_RESOURCE = "case-instances";
public static final String SEGMENT_PLAN_ITEM_INSTANCE_RESOURCE = "plan-item-instances";
public static final String SEGMENT_VARIABLES = "variables";
+ public static final String SEGMENT_VARIABLES_ASYNC = "variables-async";
public static final String SEGMENT_VARIABLE_INSTANCE_RESOURCE = "variable-instances";
public static final String SEGMENT_EVENT_SUBSCRIPTIONS = "event-subscriptions";
public static final String SEGMENT_SUBTASKS = "subtasks";
@@ -242,11 +243,21 @@ public final class CmmnRestUrls {
* URL template for case instance variable collection: cmmn-runtime/case-instances/{0:processInstanceId}/variables
*/
public static final String[] URL_CASE_INSTANCE_VARIABLE_COLLECTION = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_CASE_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES };
+
+ /**
+ * URL template for case instance variable collection: cmmn-runtime/case-instances/{0:processInstanceId}/variables-async
+ */
+ public static final String[] URL_CASE_INSTANCE_VARIABLE_ASYNC_COLLECTION = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_CASE_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES_ASYNC };
/**
* URL template for a single case instance variable: cmmn-runtime/case-instances /{0:caseInstanceId}/variables/{1:variableName}
*/
public static final String[] URL_CASE_INSTANCE_VARIABLE = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_CASE_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES, "{1}" };
+
+ /**
+ * URL template for a single case instance variable: cmmn-runtime/case-instances /{0:caseInstanceId}/variables-async/{1:variableName}
+ */
+ public static final String[] URL_CASE_INSTANCE_VARIABLE_ASYNC = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_CASE_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES_ASYNC, "{1}" };
/**
* URL template for a single case instance variable data: cmmn-runtime/case-instances/{0:processInstanceId}/variables/{1:variableName}/data
@@ -289,17 +300,25 @@ public final class CmmnRestUrls {
*/
public static final String[] URL_PLAN_ITEM_INSTANCE = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PLAN_ITEM_INSTANCE_RESOURCE, "{0}" };
-
/**
* URL template for plan item instance variables: cmmn-runtime/plan-item-instances/{0:planItemInstanceId}/variables
*/
- public static final String[] URL_PLAN_ITEM_INSTANCE_VARIABLES = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PLAN_ITEM_INSTANCE_RESOURCE, "{0}",
- SEGMENT_VARIABLES };
+ public static final String[] URL_PLAN_ITEM_INSTANCE_VARIABLES = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PLAN_ITEM_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES };
+
+ /**
+ * URL template for plan item instance variables: cmmn-runtime/plan-item-instances/{0:planItemInstanceId}/variables-async
+ */
+ public static final String[] URL_PLAN_ITEM_INSTANCE_VARIABLES_ASYNC = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PLAN_ITEM_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES_ASYNC };
+
/**
* URL template for a single plan item instance variable: cmmn-runtime/plan-item-instances/{0:planItemInstanceId}/variables/{1:variableName}
*/
- public static final String[] URL_PLAN_ITEM_INSTANCE_VARIABLE = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PLAN_ITEM_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES,
- "{1}" };
+ public static final String[] URL_PLAN_ITEM_INSTANCE_VARIABLE = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PLAN_ITEM_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES, "{1}" };
+
+ /**
+ * URL template for a single plan item instance variable: cmmn-runtime/plan-item-instances/{0:planItemInstanceId}/variables-async/{1:variableName}
+ */
+ public static final String[] URL_PLAN_ITEM_INSTANCE_VARIABLE_ASYNC = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PLAN_ITEM_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES_ASYNC, "{1}" };
/**
* URL template for a single case instance: cmmn-runtime/plan-item-instances/{0:planItemInstanceId}/variables/{1:variableName}/data
diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/BaseVariableResource.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/BaseVariableResource.java
index 78e9664c7ac..3bf2332e792 100644
--- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/BaseVariableResource.java
+++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/BaseVariableResource.java
@@ -24,9 +24,6 @@
import java.util.List;
import java.util.Map;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
import org.apache.commons.io.IOUtils;
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.flowable.cmmn.api.runtime.PlanItemInstance;
@@ -48,6 +45,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
/**
* @author Tijs Rademakers
*/
@@ -180,21 +180,22 @@ protected List processCaseVariables(CaseInstance caseInstance) {
return result;
}
- protected Object createVariable(CaseInstance caseInstance, HttpServletRequest request, HttpServletResponse response) {
- return createVariable(caseInstance.getId(), CmmnRestResponseFactory.VARIABLE_CASE, request, response, RestVariableScope.GLOBAL,
+ protected Object createVariable(CaseInstance caseInstance, boolean async, HttpServletRequest request, HttpServletResponse response) {
+ return createVariable(caseInstance.getId(), CmmnRestResponseFactory.VARIABLE_CASE, async, request, response, RestVariableScope.GLOBAL,
createVariableInterceptor(caseInstance));
}
- protected Object createVariable(PlanItemInstance planItemInstance, HttpServletRequest request, HttpServletResponse response) {
- return createVariable(planItemInstance.getId(), CmmnRestResponseFactory.VARIABLE_PLAN_ITEM, request, response, RestVariableScope.LOCAL,
+ protected Object createVariable(PlanItemInstance planItemInstance, boolean async, HttpServletRequest request, HttpServletResponse response) {
+ return createVariable(planItemInstance.getId(), CmmnRestResponseFactory.VARIABLE_PLAN_ITEM, async, request, response, RestVariableScope.LOCAL,
createVariableInterceptor(planItemInstance));
}
- protected Object createVariable(String instanceId, int variableType, HttpServletRequest request, HttpServletResponse response, RestVariableScope scope,
- VariableInterceptor variableInterceptor) {
+ protected Object createVariable(String instanceId, int variableType, boolean async, HttpServletRequest request, HttpServletResponse response,
+ RestVariableScope scope, VariableInterceptor variableInterceptor) {
+
Object result = null;
if (request instanceof MultipartHttpServletRequest) {
- result = setBinaryVariable((MultipartHttpServletRequest) request, instanceId, variableType, true, scope, variableInterceptor);
+ result = setBinaryVariable((MultipartHttpServletRequest) request, instanceId, variableType, true, async, scope, variableInterceptor);
} else {
List inputVariables = new ArrayList<>();
@@ -229,19 +230,32 @@ protected Object createVariable(String instanceId, int variableType, HttpServlet
if (!variablesToSet.isEmpty()) {
variableInterceptor.createVariables(variablesToSet);
- Map setVariables;
+ Map setVariables = null;
if (variableType == CmmnRestResponseFactory.VARIABLE_PLAN_ITEM || scope == RestVariableScope.LOCAL) {
- runtimeService.setLocalVariables(instanceId, variablesToSet);
- setVariables = runtimeService.getLocalVariables(instanceId, variablesToSet.keySet());
+ if (async) {
+ runtimeService.setLocalVariablesAsync(instanceId, variablesToSet);
+
+ } else {
+ runtimeService.setLocalVariables(instanceId, variablesToSet);
+ setVariables = runtimeService.getLocalVariables(instanceId, variablesToSet.keySet());
+ }
+
} else {
- runtimeService.setVariables(instanceId, variablesToSet);
- setVariables = runtimeService.getVariables(instanceId, variablesToSet.keySet());
+ if (async) {
+ runtimeService.setVariablesAsync(instanceId, variablesToSet);
+
+ } else {
+ runtimeService.setVariables(instanceId, variablesToSet);
+ setVariables = runtimeService.getVariables(instanceId, variablesToSet.keySet());
+ }
}
- for (RestVariable inputVariable : inputVariables) {
- String variableName = inputVariable.getName();
- Object variableValue = setVariables.get(variableName);
- resultVariables.add(restResponseFactory.createRestVariable(variableName, variableValue, scope, instanceId, variableType, false));
+ if (!async) {
+ for (RestVariable inputVariable : inputVariables) {
+ String variableName = inputVariable.getName();
+ Object variableValue = setVariables.get(variableName);
+ resultVariables.add(restResponseFactory.createRestVariable(variableName, variableValue, scope, instanceId, variableType, false));
+ }
}
}
}
@@ -265,23 +279,29 @@ public void deleteAllVariables(CaseInstance caseInstance) {
runtimeService.removeVariables(caseInstance.getId(), currentVariables);
}
- protected RestVariable setSimpleVariable(RestVariable restVariable, String instanceId, boolean isNew, RestVariableScope scope, int variableType, VariableInterceptor variableInterceptor) {
+ protected RestVariable setSimpleVariable(RestVariable restVariable, String instanceId, boolean isNew, boolean async, RestVariableScope scope, int variableType, VariableInterceptor variableInterceptor) {
if (restVariable.getName() == null) {
throw new FlowableIllegalArgumentException("Variable name is required");
}
Object actualVariableValue = restResponseFactory.getVariableValue(restVariable);
- setVariable(instanceId, restVariable.getName(), actualVariableValue, scope, isNew, variableInterceptor);
+ setVariable(instanceId, restVariable.getName(), actualVariableValue, scope, isNew, async, variableInterceptor);
- RestVariable variable = getVariableFromRequestWithoutAccessCheck(instanceId, restVariable.getName(), variableType, false);
- // We are setting the scope because the fetched variable does not have it
- variable.setVariableScope(scope);
+ RestVariable variable = null;
+
+ if (!async) {
+ variable = getVariableFromRequestWithoutAccessCheck(instanceId, restVariable.getName(), variableType, false);
+
+ // We are setting the scope because the fetched variable does not have it
+ variable.setVariableScope(scope);
+ }
+
return variable;
}
protected RestVariable setBinaryVariable(MultipartHttpServletRequest request, String instanceId, int responseVariableType, boolean isNew,
- RestVariableScope scope, VariableInterceptor variableInterceptor) {
+ boolean async, RestVariableScope scope, VariableInterceptor variableInterceptor) {
// Validate input and set defaults
if (request.getFileMap().size() == 0) {
@@ -338,31 +358,39 @@ protected RestVariable setBinaryVariable(MultipartHttpServletRequest request, St
if (variableType.equals(CmmnRestResponseFactory.BYTE_ARRAY_VARIABLE_TYPE)) {
// Use raw bytes as variable value
byte[] variableBytes = IOUtils.toByteArray(file.getInputStream());
- setVariable(instanceId, variableName, variableBytes, scope, isNew, variableInterceptor);
+ setVariable(instanceId, variableName, variableBytes, scope, isNew, async, variableInterceptor);
} else if (isSerializableVariableAllowed) {
// Try deserializing the object
ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
Object value = stream.readObject();
- setVariable(instanceId, variableName, value, scope, isNew, variableInterceptor);
+ setVariable(instanceId, variableName, value, scope, isNew, async, variableInterceptor);
stream.close();
} else {
throw new FlowableContentNotSupportedException("Serialized objects are not allowed");
}
- RestVariable restVariable = getVariableFromRequestWithoutAccessCheck(instanceId, variableName, responseVariableType, false);
- // We are setting the scope because the fetched variable does not have it
- restVariable.setVariableScope(scope);
+ RestVariable restVariable = null;
+
+ if (!async) {
+ restVariable = getVariableFromRequestWithoutAccessCheck(instanceId, variableName, responseVariableType, false);
+
+ // We are setting the scope because the fetched variable does not have it
+ restVariable.setVariableScope(scope);
+ }
+
return restVariable;
+
} catch (IOException ioe) {
throw new FlowableIllegalArgumentException("Could not process multipart content", ioe);
+
} catch (ClassNotFoundException ioe) {
throw new FlowableContentNotSupportedException(
"The provided body contains a serialized object for which the class was not found: " + ioe.getMessage());
}
}
- protected void setVariable(String instanceId, String name, Object value, RestVariableScope scope, boolean isNew, VariableInterceptor variableInterceptor) {
+ protected void setVariable(String instanceId, String name, Object value, RestVariableScope scope, boolean isNew, boolean async, VariableInterceptor variableInterceptor) {
if (isNew) {
variableInterceptor.createVariables(Collections.singletonMap(name, value));
} else {
@@ -374,9 +402,19 @@ protected void setVariable(String instanceId, String name, Object value, RestVar
if (isNew && runtimeService.hasLocalVariable(instanceId, name)) {
throw new FlowableConflictException("Local variable '" + name + "' is already present on plan item instance '" + instanceId + "'.");
}
- runtimeService.setLocalVariable(instanceId, name, value);
+
+ if (async) {
+ runtimeService.setLocalVariableAsync(instanceId, name, value);
+ } else {
+ runtimeService.setLocalVariable(instanceId, name, value);
+ }
+
} else {
- runtimeService.setVariable(instanceId, name, value);
+ if (async) {
+ runtimeService.setVariableAsync(instanceId, name, value);
+ } else {
+ runtimeService.setVariable(instanceId, name, value);
+ }
}
}
diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceVariableCollectionResource.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceVariableCollectionResource.java
index d1ec8456eef..d3d0da42776 100644
--- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceVariableCollectionResource.java
+++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceVariableCollectionResource.java
@@ -15,9 +15,6 @@
import java.util.List;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.flowable.cmmn.rest.service.api.engine.variable.RestVariable;
import org.springframework.http.HttpStatus;
@@ -37,6 +34,8 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
/**
* @author Tijs Rademakers
@@ -85,7 +84,35 @@ public List getVariables(@ApiParam(name = "caseInstanceId") @PathV
public Object createOrUpdateExecutionVariable(@ApiParam(name = "caseInstanceId") @PathVariable String caseInstanceId, HttpServletRequest request, HttpServletResponse response) {
CaseInstance caseInstance = getCaseInstanceFromRequestWithoutAccessCheck(caseInstanceId);
- return createVariable(caseInstance, request, response);
+ return createVariable(caseInstance, false, request, response);
+ }
+
+ @ApiOperation(value = "Update a multiple/single (non)binary variable on a case instance asynchronously", tags = { "Case Instance Variables" }, nickname = "createOrUpdateCaseVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable or an array of RestVariable) or by passing a multipart/form-data Object.\n"
+ + "Nonexistent variables are created on the case-instance and existing ones are overridden without any error.\n"
+ + "Any number of variables can be passed into the request body array.\n"
+ + "Note that scope is ignored, only global variables can be set in a case instance.\n"
+ + "NB: Swagger V2 specification doesn't support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.cmmn.service.api.engine.variable.RestVariable", value = "Create a variable on a case instance", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "Simple content item"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to update the variables has been created."),
+ @ApiResponse(code = 400, message = "Indicates the request body is incomplete or contains illegal values. The status description contains additional information about the error."),
+ @ApiResponse(code = 415, message = "Indicates the serializable data contains an object for which no class is present in the JVM running the Flowable engine and therefore cannot be deserialized.")
+
+ })
+ @PutMapping(value = "/cmmn-runtime/case-instances/{caseInstanceId}/variables-async", consumes = {"application/json", "multipart/form-data"})
+ public void createOrUpdateExecutionVariableAsync(@ApiParam(name = "caseInstanceId") @PathVariable String caseInstanceId, HttpServletRequest request, HttpServletResponse response) {
+ CaseInstance caseInstance = getCaseInstanceFromRequestWithoutAccessCheck(caseInstanceId);
+ createVariable(caseInstance, true, request, response);
}
@ApiOperation(value = "Create variables or new binary variable on a case instance", tags = { "Case Instance Variables" }, nickname = "createCaseInstanceVariable",
@@ -111,11 +138,38 @@ public Object createOrUpdateExecutionVariable(@ApiParam(name = "caseInstanceId")
@ApiResponse(code = 409, message = "Indicates the case instance was found but already contains a variable with the given name (only thrown when POST method is used). Use the update-method instead."),
})
-
- @PostMapping(value = "/cmmn-runtime/case-instances/{caseInstanceId}/variables", produces = "application/json", consumes = {"application/json", "multipart/form-data", "text/plain"})
+ @PostMapping(value = "/cmmn-runtime/case-instances/{caseInstanceId}/variables", consumes = {"application/json", "multipart/form-data", "text/plain"})
public Object createExecutionVariable(@ApiParam(name = "caseInstanceId") @PathVariable String caseInstanceId, HttpServletRequest request, HttpServletResponse response) {
CaseInstance caseInstance = getCaseInstanceFromRequestWithoutAccessCheck(caseInstanceId);
- return createVariable(caseInstance, request, response);
+ return createVariable(caseInstance, false, request, response);
+ }
+
+ @ApiOperation(value = "Create variables or new binary variable on a case instance asynchronously", tags = { "Case Instance Variables" }, nickname = "createCaseInstanceVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable or an array of RestVariable) or by passing a multipart/form-data Object.\n"
+ + "Nonexistent variables are created on the case-instance and existing ones are overridden without any error.\n"
+ + "Any number of variables can be passed into the request body array.\n"
+ + "Note that scope is ignored, only global variables can be set in a case instance.\n"
+ + "NB: Swagger V2 specification doesn't support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.cmmn.service.api.engine.variable.RestVariable", value = "Create a variable on a case instance", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "Simple content item"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to create the variables has been created."),
+ @ApiResponse(code = 400, message = "Indicates the request body is incomplete or contains illegal values. The status description contains additional information about the error."),
+ @ApiResponse(code = 409, message = "Indicates the case instance contains a variable with the given name (only thrown when POST method is used). Use the update-method instead."),
+
+ })
+ @PostMapping(value = "/cmmn-runtime/case-instances/{caseInstanceId}/variables-async", produces = "application/json", consumes = {"application/json", "multipart/form-data", "text/plain"})
+ public void createExecutionVariableAsync(@ApiParam(name = "caseInstanceId") @PathVariable String caseInstanceId, HttpServletRequest request, HttpServletResponse response) {
+ CaseInstance caseInstance = getCaseInstanceFromRequestWithoutAccessCheck(caseInstanceId);
+ createVariable(caseInstance, true, request, response);
}
@ApiOperation(value = "Delete all variables", tags = { "Case Instance Variables" }, nickname = "deleteCaseVariable", code = 204)
diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceVariableResource.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceVariableResource.java
index 025753c98f1..addb466d2e4 100644
--- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceVariableResource.java
+++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceVariableResource.java
@@ -15,8 +15,6 @@
import java.util.Collections;
-import jakarta.servlet.http.HttpServletRequest;
-
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.flowable.cmmn.rest.service.api.CmmnRestResponseFactory;
import org.flowable.cmmn.rest.service.api.engine.variable.RestVariable;
@@ -44,6 +42,7 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
+import jakarta.servlet.http.HttpServletRequest;
/**
* @author Tijs Rademakers
@@ -96,7 +95,7 @@ public RestVariable updateVariable(@ApiParam(name = "caseInstanceId") @PathVaria
RestVariable result = null;
if (request instanceof MultipartHttpServletRequest) {
result = setBinaryVariable((MultipartHttpServletRequest) request, caseInstance.getId(), CmmnRestResponseFactory.VARIABLE_CASE, false,
- RestVariable.RestVariableScope.GLOBAL, createVariableInterceptor(caseInstance));
+ false, RestVariable.RestVariableScope.GLOBAL, createVariableInterceptor(caseInstance));
if (!result.getName().equals(variableName)) {
throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
@@ -117,10 +116,59 @@ public RestVariable updateVariable(@ApiParam(name = "caseInstanceId") @PathVaria
throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
}
- result = setSimpleVariable(restVariable, caseInstance.getId(), false, RestVariable.RestVariableScope.GLOBAL, CmmnRestResponseFactory.VARIABLE_CASE, createVariableInterceptor(caseInstance));
+ result = setSimpleVariable(restVariable, caseInstance.getId(), false, false, RestVariable.RestVariableScope.GLOBAL, CmmnRestResponseFactory.VARIABLE_CASE, createVariableInterceptor(caseInstance));
}
return result;
}
+
+ @ApiOperation(value = "Update a single variable on a case instance asynchronously", tags = { "Case Instance Variables" }, nickname = "updateCaseInstanceVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ + "Nonexistent variables are created on the case instance and existing ones are overridden without any error.\n"
+ + "Note that scope is ignored, only global variables can be set in a case instance.\n"
+ + "NB: Swagger V2 specification doesn't support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.cmmn.service.api.engine.variable.RestVariable", value = "Create a variable on a case instance", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "Simple content item"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to update the variable has been created."),
+ @ApiResponse(code = 404, message = "Indicates the case instance does not have a variable with the given name. Status description contains additional information about the error.")
+ })
+ @PutMapping(value = "/cmmn-runtime/case-instances/{caseInstanceId}/variables-async/{variableName}", consumes = {"application/json", "multipart/form-data"})
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void updateVariableAsync(@ApiParam(name = "caseInstanceId") @PathVariable("caseInstanceId") String caseInstanceId, @ApiParam(name = "variableName") @PathVariable("variableName") String variableName,
+ HttpServletRequest request) {
+
+ CaseInstance caseInstance = getCaseInstanceFromRequestWithoutAccessCheck(caseInstanceId);
+
+ if (request instanceof MultipartHttpServletRequest) {
+ setBinaryVariable((MultipartHttpServletRequest) request, caseInstance.getId(), CmmnRestResponseFactory.VARIABLE_CASE, false,
+ true, RestVariable.RestVariableScope.GLOBAL, createVariableInterceptor(caseInstance));
+
+ } else {
+ RestVariable restVariable = null;
+ try {
+ restVariable = objectMapper.readValue(request.getInputStream(), RestVariable.class);
+ } catch (Exception e) {
+ throw new FlowableIllegalArgumentException("request body could not be transformed to a RestVariable instance.", e);
+ }
+
+ if (restVariable == null) {
+ throw new FlowableException("Invalid body was supplied");
+ }
+ if (!restVariable.getName().equals(variableName)) {
+ throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
+ }
+
+ setSimpleVariable(restVariable, caseInstance.getId(), false, true, RestVariable.RestVariableScope.GLOBAL, CmmnRestResponseFactory.VARIABLE_CASE, createVariableInterceptor(caseInstance));
+ }
+ }
@ApiOperation(value = "Delete a variable", tags = { "Case Instance Variables" }, nickname = "deleteCaseInstanceVariable", code = 204)
@ApiResponses(value = {
diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/PlanItemInstanceVariableResource.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/PlanItemInstanceVariableResource.java
index 796e9695751..97e63230073 100644
--- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/PlanItemInstanceVariableResource.java
+++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/PlanItemInstanceVariableResource.java
@@ -15,8 +15,6 @@
import java.util.Collections;
-import jakarta.servlet.http.HttpServletRequest;
-
import org.flowable.cmmn.api.runtime.PlanItemInstance;
import org.flowable.cmmn.engine.CmmnEngineConfiguration;
import org.flowable.cmmn.rest.service.api.CmmnRestResponseFactory;
@@ -43,6 +41,7 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
+import jakarta.servlet.http.HttpServletRequest;
/**
* @author Christopher Welsch
@@ -54,7 +53,6 @@ public class PlanItemInstanceVariableResource extends BaseVariableResource {
@Autowired
protected CmmnEngineConfiguration cmmnEngineConfiguration;
- // FIXME OASv3 to solve Multiple Endpoint issue
@ApiOperation(value = "Update a variable on a plan item", tags = { "Plan Item Instances" }, nickname = "updatePlanItemVariable",
notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
@@ -86,7 +84,7 @@ public RestVariable updateVariable(@ApiParam(name = "planItemInstanceId") @PathV
RestVariable result = null;
if (request instanceof MultipartHttpServletRequest) {
result = setBinaryVariable((MultipartHttpServletRequest) request, planItem.getId(), CmmnRestResponseFactory.VARIABLE_PLAN_ITEM, false,
- RestVariable.RestVariableScope.LOCAL, createVariableInterceptor(planItem));
+ false, RestVariable.RestVariableScope.LOCAL, createVariableInterceptor(planItem));
if (!result.getName().equals(variableName)) {
throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
@@ -107,10 +105,61 @@ public RestVariable updateVariable(@ApiParam(name = "planItemInstanceId") @PathV
throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
}
- result = setSimpleVariable(restVariable, planItem.getId(), false, RestVariable.RestVariableScope.LOCAL, CmmnRestResponseFactory.VARIABLE_PLAN_ITEM, createVariableInterceptor(planItem));
+ result = setSimpleVariable(restVariable, planItem.getId(), false, false, RestVariable.RestVariableScope.LOCAL, CmmnRestResponseFactory.VARIABLE_PLAN_ITEM, createVariableInterceptor(planItem));
}
return result;
}
+
+ @ApiOperation(value = "Update a variable on a plan item asynchronously", tags = { "Plan Item Instances" }, nickname = "updatePlanItemVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ + "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.service.api.engine.variable.RestVariable", value = "Update a variable on a plan item instance", paramType = "body", example =
+ "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " \"scope\":\"global\"\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "intProcVar"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ @ApiImplicitParam(name = "scope", dataType = "string", paramType = "form", example = "global"),
+
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 200, message = "Indicates the job to update a variable is created."),
+ @ApiResponse(code = 404, message = "Indicates the plan item instance does not have a variable with the given name. Status description contains additional information about the error.")
+ })
+ @PutMapping(value = "/cmmn-runtime/plan-item-instances/{planItemInstanceId}/variables-async/{variableName}", consumes = {"application/json", "multipart/form-data" })
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void updateVariableAsync(@ApiParam(name = "planItemInstanceId") @PathVariable("planItemInstanceId") String planItemInstanceId,
+ @ApiParam(name = "variableName") @PathVariable("variableName") String variableName, HttpServletRequest request) {
+
+ PlanItemInstance planItem = getPlanItemInstanceFromRequest(planItemInstanceId);
+
+ if (request instanceof MultipartHttpServletRequest) {
+ setBinaryVariable((MultipartHttpServletRequest) request, planItem.getId(), CmmnRestResponseFactory.VARIABLE_PLAN_ITEM, false,
+ true, RestVariable.RestVariableScope.LOCAL, createVariableInterceptor(planItem));
+
+ } else {
+ RestVariable restVariable = null;
+ try {
+ restVariable = objectMapper.readValue(request.getInputStream(), RestVariable.class);
+ } catch (Exception e) {
+ throw new FlowableIllegalArgumentException("Error converting request body to RestVariable instance", e);
+ }
+
+ if (restVariable == null) {
+ throw new FlowableException("Invalid body was supplied");
+ }
+ if (!restVariable.getName().equals(variableName)) {
+ throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
+ }
+
+ setSimpleVariable(restVariable, planItem.getId(), false, true, RestVariable.RestVariableScope.LOCAL, CmmnRestResponseFactory.VARIABLE_PLAN_ITEM, createVariableInterceptor(planItem));
+ }
+ }
@ApiOperation(value = "Delete a variable for a plan item instance", tags = { "Plan Item Instances" }, nickname = "deletePlanItemVariable", code = 204)
@ApiResponses(value = {
diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/planitem/PlanItemInstanceVariableCollectionResource.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/planitem/PlanItemInstanceVariableCollectionResource.java
index a8ac56187d4..d7f31a9d089 100644
--- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/planitem/PlanItemInstanceVariableCollectionResource.java
+++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/planitem/PlanItemInstanceVariableCollectionResource.java
@@ -13,9 +13,6 @@
package org.flowable.cmmn.rest.service.api.runtime.planitem;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
import org.flowable.cmmn.api.runtime.PlanItemInstance;
import org.flowable.cmmn.engine.CmmnEngineConfiguration;
import org.flowable.cmmn.rest.service.api.runtime.caze.BaseVariableResource;
@@ -32,6 +29,8 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
/**
* @author Christopher Welsch
@@ -43,7 +42,6 @@ public class PlanItemInstanceVariableCollectionResource extends BaseVariableReso
@Autowired
protected CmmnEngineConfiguration cmmnEngineConfiguration;
- // FIXME OASv3 to solve Multiple Endpoint issue
@ApiOperation(value = "Create a variable on a plan item", tags = { "Plan Item Instances" }, nickname = "createPlanItemInstanceVariable",
notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
@@ -73,7 +71,37 @@ public Object createPlanItemInstanceVariable(@ApiParam(name = "planItemInstanceI
HttpServletRequest request, HttpServletResponse response) {
PlanItemInstance planItem = getPlanItemInstanceFromRequest(planItemInstanceId);
- return createVariable(planItem, request, response);
+ return createVariable(planItem, false, request, response);
+ }
+
+ @ApiOperation(value = "Create a variable on a plan item asynchronously", tags = { "Plan Item Instances" }, nickname = "createPlanItemInstanceVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ + "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.cmmn.rest.service.api.engine.variable.RestVariable", value = "Create a variable on a plan item instance", paramType = "body", example =
+ "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " \"scope\":\"global\"\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "intProcVar"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ @ApiImplicitParam(name = "scope", dataType = "string", paramType = "form", example = "global"),
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to create a variable is created."),
+ @ApiResponse(code = 400, message = "Indicates the request body is incomplete or contains illegal values. The status description contains additional information about the error."),
+ @ApiResponse(code = 404, message = "Indicates the plan item instance does not have a variable with the given name. Status description contains additional information about the error."),
+ @ApiResponse(code = 409, message = "Indicates the plan item instance already contains a variable with the given name. Use the update-method instead.")
+ })
+ @PostMapping(value = "/cmmn-runtime/plan-item-instances/{planItemInstanceId}/variables-async", consumes = {"application/json", "multipart/form-data" })
+ public void createPlanItemInstanceVariableAsync(@ApiParam(name = "planItemInstanceId") @PathVariable("planItemInstanceId") String planItemInstanceId,
+ HttpServletRequest request, HttpServletResponse response) {
+
+ PlanItemInstance planItem = getPlanItemInstanceFromRequest(planItemInstanceId);
+ createVariable(planItem, true, request, response);
}
}
diff --git a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariableResourceTest.java b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariableResourceTest.java
index 5c2c2f806cb..edd4c2de97c 100644
--- a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariableResourceTest.java
+++ b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariableResourceTest.java
@@ -40,6 +40,7 @@
import org.flowable.cmmn.rest.service.BaseSpringRestTestCase;
import org.flowable.cmmn.rest.service.HttpMultipartHelper;
import org.flowable.cmmn.rest.service.api.CmmnRestUrls;
+import org.flowable.job.api.Job;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -427,4 +428,37 @@ public void testUpdateBinaryCaseVariable() throws Exception {
assertThat(variableValue).isInstanceOf(byte[].class);
assertThat(new String((byte[]) variableValue)).isEqualTo("This is binary content");
}
+
+ @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
+ public void testUpdateCaseVariableAsync() throws Exception {
+ CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase")
+ .variables(Collections.singletonMap("overlappingVariable", (Object) "processValue")).start();
+ runtimeService.setVariable(caseInstance.getId(), "myVar", "value");
+
+ // Update variable
+ ObjectNode requestNode = objectMapper.createObjectNode();
+ requestNode.put("name", "myVar");
+ requestNode.put("value", "updatedValue");
+ requestNode.put("type", "string");
+
+ HttpPut httpPut = new HttpPut(
+ SERVER_URL_PREFIX + CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_CASE_INSTANCE_VARIABLE_ASYNC, caseInstance.getId(), "myVar"));
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ CloseableHttpResponse response = executeRequest(httpPut, HttpStatus.SC_NO_CONTENT);
+ closeResponse(response);
+
+ assertThat(runtimeService.getVariable(caseInstance.getId(), "myVar")).isEqualTo("value");
+
+ Job job = managementService.createJobQuery().caseInstanceId(caseInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ assertThat(runtimeService.getVariable(caseInstance.getId(), "myVar")).isEqualTo("updatedValue");
+
+ // Try updating with mismatch between URL and body variableName
+ requestNode.put("name", "unexistingVariable");
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ closeResponse(executeRequest(httpPut, HttpStatus.SC_BAD_REQUEST));
+ }
}
diff --git a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariablesCollectionResourceTest.java b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariablesCollectionResourceTest.java
index 44afa8650e2..4a5d596729a 100644
--- a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariablesCollectionResourceTest.java
+++ b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/CaseInstanceVariablesCollectionResourceTest.java
@@ -37,6 +37,7 @@
import org.flowable.cmmn.rest.service.BaseSpringRestTestCase;
import org.flowable.cmmn.rest.service.HttpMultipartHelper;
import org.flowable.cmmn.rest.service.api.CmmnRestUrls;
+import org.flowable.job.api.Job;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -87,7 +88,7 @@ public void testGetCaseVariables() throws Exception {
* Test creating a single case variable. POST cmmn-runtime/case-instance/{caseInstanceId}/variables
*/
@CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
- public void testCreateSingleProcessInstanceVariable() throws Exception {
+ public void testCreateSingleCaseInstanceVariable() throws Exception {
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start();
ArrayNode requestNode = objectMapper.createArrayNode();
@@ -121,7 +122,7 @@ public void testCreateSingleProcessInstanceVariable() throws Exception {
* Test creating a single case variable using a binary stream. POST cmmn-runtime/case-instances/{caseInstanceId}/variables
*/
@CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
- public void testCreateSingleBinaryProcessVariable() throws Exception {
+ public void testCreateSingleBinaryCaseVariable() throws Exception {
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start();
InputStream binaryContent = new ByteArrayInputStream("This is binary content".getBytes());
@@ -159,7 +160,7 @@ public void testCreateSingleBinaryProcessVariable() throws Exception {
* Test creating a single process variable using a binary stream containing a serializable. POST runtime/process-instances/{processInstanceId}/variables
*/
@CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
- public void testCreateSingleSerializableProcessVariable() throws Exception {
+ public void testCreateSingleSerializableCaseVariable() throws Exception {
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start();
TestSerializableVariable serializable = new TestSerializableVariable();
@@ -256,7 +257,7 @@ public void testCreateSingleCaseVariableEdgeCases() throws Exception {
* Test creating a single case variable, testing default types when omitted. POST cmmn-runtime/case-instances/{caseInstanceId}/variables
*/
@CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
- public void testCreateSingleProcessVariableDefaultTypes() throws Exception {
+ public void testCreateSingleCaseVariableDefaultTypes() throws Exception {
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start();
// String type detection
@@ -304,7 +305,7 @@ public void testCreateSingleProcessVariableDefaultTypes() throws Exception {
* Test creating multiple case variables in a single call. POST cmmn-runtime/case-instance/{caseInstanceId}/variables
*/
@CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
- public void testCreateMultipleProcessVariables() throws Exception {
+ public void testCreateMultipleCaseVariables() throws Exception {
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start();
ArrayNode requestNode = objectMapper.createArrayNode();
@@ -420,6 +421,145 @@ public void testCreateMultipleCaseVariablesWithOverride() throws Exception {
entry("stringVariable2", "another string value")
);
}
+
+ @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
+ public void testCreateSingleCaseInstanceVariableAsync() throws Exception {
+ CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start();
+
+ ArrayNode requestNode = objectMapper.createArrayNode();
+ ObjectNode variableNode = requestNode.addObject();
+ variableNode.put("name", "myVariable");
+ variableNode.put("value", "simple string value");
+ variableNode.put("type", "string");
+
+ // Create a new local variable
+ HttpPost httpPost = new HttpPost(
+ SERVER_URL_PREFIX + CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_CASE_INSTANCE_VARIABLE_ASYNC_COLLECTION, caseInstance.getId()));
+ httpPost.setEntity(new StringEntity(requestNode.toString()));
+ CloseableHttpResponse response = executeBinaryRequest(httpPost, HttpStatus.SC_CREATED);
+ closeResponse(response);
+
+ assertThat(runtimeService.hasVariable(caseInstance.getId(), "myVariable")).isFalse();
+
+ Job job = managementService.createJobQuery().caseInstanceId(caseInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ assertThat(runtimeService.hasVariable(caseInstance.getId(), "myVariable")).isTrue();
+ assertThat(runtimeService.getVariable(caseInstance.getId(), "myVariable")).isEqualTo("simple string value");
+ }
+
+ @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
+ public void testCreateSingleBinaryCaseVariableAsync() throws Exception {
+ CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start();
+
+ InputStream binaryContent = new ByteArrayInputStream("This is binary content".getBytes());
+
+ // Add name, type and scope
+ Map additionalFields = new HashMap<>();
+ additionalFields.put("name", "binaryVariable");
+ additionalFields.put("type", "binary");
+
+ HttpPost httpPost = new HttpPost(
+ SERVER_URL_PREFIX + CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_CASE_INSTANCE_VARIABLE_ASYNC_COLLECTION, caseInstance.getId()));
+ httpPost.setEntity(HttpMultipartHelper.getMultiPartEntity("value", "application/octet-stream", binaryContent, additionalFields));
+ CloseableHttpResponse response = executeBinaryRequest(httpPost, HttpStatus.SC_CREATED);
+ closeResponse(response);
+
+ assertThat(runtimeService.hasVariable(caseInstance.getId(), "binaryVariable")).isFalse();
+
+ Job job = managementService.createJobQuery().caseInstanceId(caseInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ // Check actual value of variable in engine
+ Object variableValue = runtimeService.getVariable(caseInstance.getId(), "binaryVariable");
+ assertThat(variableValue).isInstanceOf(byte[].class);
+ assertThat(new String((byte[]) variableValue)).isEqualTo("This is binary content");
+ }
+
+ @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
+ public void testCreateMultipleCaseVariablesAsync() throws Exception {
+ CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start();
+
+ ArrayNode requestNode = objectMapper.createArrayNode();
+
+ // String variable
+ ObjectNode stringVarNode = requestNode.addObject();
+ stringVarNode.put("name", "stringVariable");
+ stringVarNode.put("value", "simple string value");
+ stringVarNode.put("type", "string");
+
+ // Integer
+ ObjectNode integerVarNode = requestNode.addObject();
+ integerVarNode.put("name", "integerVariable");
+ integerVarNode.put("value", 1234);
+ integerVarNode.put("type", "integer");
+
+ // Short
+ ObjectNode shortVarNode = requestNode.addObject();
+ shortVarNode.put("name", "shortVariable");
+ shortVarNode.put("value", 123);
+ shortVarNode.put("type", "short");
+
+ // Long
+ ObjectNode longVarNode = requestNode.addObject();
+ longVarNode.put("name", "longVariable");
+ longVarNode.put("value", 4567890L);
+ longVarNode.put("type", "long");
+
+ // Double
+ ObjectNode doubleVarNode = requestNode.addObject();
+ doubleVarNode.put("name", "doubleVariable");
+ doubleVarNode.put("value", 123.456);
+ doubleVarNode.put("type", "double");
+
+ // Boolean
+ ObjectNode booleanVarNode = requestNode.addObject();
+ booleanVarNode.put("name", "booleanVariable");
+ booleanVarNode.put("value", Boolean.TRUE);
+ booleanVarNode.put("type", "boolean");
+
+ // Date
+ Calendar varCal = Calendar.getInstance();
+ String isoString = getISODateString(varCal.getTime());
+
+ ObjectNode dateVarNode = requestNode.addObject();
+ dateVarNode.put("name", "dateVariable");
+ dateVarNode.put("value", isoString);
+ dateVarNode.put("type", "date");
+
+ // Create local variables with a single request
+ HttpPost httpPost = new HttpPost(
+ SERVER_URL_PREFIX + CmmnRestUrls.createRelativeResourceUrl(CmmnRestUrls.URL_CASE_INSTANCE_VARIABLE_ASYNC_COLLECTION, caseInstance.getId()));
+ httpPost.setEntity(new StringEntity(requestNode.toString()));
+ CloseableHttpResponse response = executeRequest(httpPost, HttpStatus.SC_CREATED);
+ closeResponse(response);
+
+ assertThat(runtimeService.hasVariable(caseInstance.getId(), "stringVariable")).isFalse();
+ assertThat(runtimeService.hasVariable(caseInstance.getId(), "integerVariable")).isFalse();
+
+ Job job = managementService.createJobQuery().caseInstanceId(caseInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ // Check if engine has correct variables set
+ Map variables = runtimeService.getVariables(caseInstance.getId());
+
+ assertThat(variables)
+ .containsOnly(
+ entry("stringVariable", "simple string value"),
+ entry("integerVariable", 1234),
+ entry("shortVariable", (short) 123),
+ entry("longVariable", 4567890L),
+ entry("doubleVariable", 123.456),
+ entry("booleanVariable", Boolean.TRUE),
+ entry("dateVariable", longDateFormat.parse(isoString))
+ );
+ }
/**
* Test deleting all case variables. DELETE cmmn-runtime/case-instance/{caseInstanceId}/variables
diff --git a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/PlanItemInstanceVariableCollectionResourceTest.java b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/PlanItemInstanceVariableCollectionResourceTest.java
index bafffada05a..7291e912a72 100644
--- a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/PlanItemInstanceVariableCollectionResourceTest.java
+++ b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/PlanItemInstanceVariableCollectionResourceTest.java
@@ -34,6 +34,7 @@
import org.flowable.cmmn.rest.service.BaseSpringRestTestCase;
import org.flowable.cmmn.rest.service.HttpMultipartHelper;
import org.flowable.cmmn.rest.service.api.CmmnRestUrls;
+import org.flowable.job.api.Job;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -137,5 +138,37 @@ public void testCreateBinaryPlanItemInstanceVariable() throws Exception {
assertThat(variableValue).isInstanceOf(byte[].class);
assertThat(new String((byte[]) variableValue)).isEqualTo("This is binary content");
}
+
+ @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
+ public void testCreatePlanItemInstanceVariableAsync() throws Exception {
+ CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").businessKey("myBusinessKey").start();
+
+ List planItems = runtimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).list();
+ assertThat(planItems).hasSize(1);
+ PlanItemInstance planItem = planItems.get(0);
+
+ String url = buildUrl(CmmnRestUrls.URL_PLAN_ITEM_INSTANCE_VARIABLES_ASYNC, planItem.getId());
+ ArrayNode requestNode = objectMapper.createArrayNode();
+ ObjectNode body = requestNode.addObject();
+
+ body.put("name", "testLocalVar");
+ body.put("value", "newTestValue");
+ body.put("type", "string");
+
+ HttpPost postRequest = new HttpPost(url);
+ postRequest.setEntity(new StringEntity(requestNode.toString()));
+ CloseableHttpResponse response = executeRequest(postRequest, HttpStatus.SC_CREATED);
+ closeResponse(response);
+
+ assertThat(runtimeService.hasLocalVariable(planItem.getId(), "testLocalVar")).isFalse();
+
+ Job job = managementService.createJobQuery().caseInstanceId(caseInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ // Check resulting instance
+ assertThat(runtimeService.getLocalVariable(planItem.getId(), "testLocalVar")).isEqualTo("newTestValue");
+ }
}
diff --git a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/PlanItemVariableResourceTest.java b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/PlanItemVariableResourceTest.java
index 088469564d2..72cb7fa85a0 100644
--- a/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/PlanItemVariableResourceTest.java
+++ b/modules/flowable-cmmn-rest/src/test/java/org/flowable/cmmn/rest/service/api/runtime/PlanItemVariableResourceTest.java
@@ -37,6 +37,7 @@
import org.flowable.cmmn.rest.service.BaseSpringRestTestCase;
import org.flowable.cmmn.rest.service.HttpMultipartHelper;
import org.flowable.cmmn.rest.service.api.CmmnRestUrls;
+import org.flowable.job.api.Job;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -83,7 +84,6 @@ public void testUpdatePlanItemInstanceVariable() throws Exception {
// Check resulting instance
assertThat(runtimeService.getLocalVariable(planItem.getId(), "testLocalVar")).isEqualTo("newTestValue");
-
}
@CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
@@ -108,6 +108,39 @@ public void testUpdatePlanItemInstanceVariableExceptions() throws Exception {
CloseableHttpResponse response = executeRequest(httpPut, HttpStatus.SC_BAD_REQUEST);
closeResponse(response);
}
+
+ @CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
+ public void testUpdatePlanItemInstanceVariableAsync() throws Exception {
+ CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").businessKey("myBusinessKey").start();
+
+ List planItems = runtimeService.createPlanItemInstanceQuery().caseInstanceId(caseInstance.getId()).list();
+ assertThat(planItems).hasSize(1);
+ PlanItemInstance planItem = planItems.get(0);
+
+ runtimeService.setLocalVariable(planItem.getId(), "testLocalVar", "testVarValue");
+
+ String url = buildUrl(CmmnRestUrls.URL_PLAN_ITEM_INSTANCE_VARIABLE_ASYNC, planItem.getId(), "testLocalVar");
+ ObjectNode body = objectMapper.createObjectNode();
+
+ body.put("name", "testLocalVar");
+ body.put("value", "newTestValue");
+ body.put("type", "string");
+
+ HttpPut putRequest = new HttpPut(url);
+ putRequest.setEntity(new StringEntity(body.toString()));
+ CloseableHttpResponse response = executeRequest(putRequest, HttpStatus.SC_NO_CONTENT);
+ closeResponse(response);
+
+ assertThat(runtimeService.getLocalVariable(planItem.getId(), "testLocalVar")).isEqualTo("testVarValue");
+
+ Job job = managementService.createJobQuery().caseInstanceId(caseInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ // Check resulting instance
+ assertThat(runtimeService.getLocalVariable(planItem.getId(), "testLocalVar")).isEqualTo("newTestValue");
+ }
@CmmnDeployment(resources = { "org/flowable/cmmn/rest/service/api/repository/oneHumanTaskCase.cmmn" })
public void testDeleteExecutionVariable() throws Exception {
diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/RestUrls.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/RestUrls.java
index 0ed63c6d7c8..6701da99909 100644
--- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/RestUrls.java
+++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/RestUrls.java
@@ -43,6 +43,7 @@ public final class RestUrls {
public static final String SEGMENT_ACTIVITY_INSTANCE_RESOURCE = "activity-instances";
public static final String SEGMENT_PROCESS_INSTANCE_RESOURCE = "process-instances";
public static final String SEGMENT_VARIABLES = "variables";
+ public static final String SEGMENT_VARIABLES_ASYNC = "variables-async";
public static final String SEGMENT_VARIABLE_INSTANCE_RESOURCE = "variable-instances";
public static final String SEGMENT_EVENT_SUBSCRIPTIONS = "event-subscriptions";
public static final String SEGMENT_SUBTASKS = "subtasks";
@@ -295,6 +296,11 @@ public final class RestUrls {
* URL template for a single variables for an execution: runtime/executions/{0:executionId}/variables/{1:variableName}
*/
public static final String[] URL_EXECUTION_VARIABLE = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_EXECUTION_RESOURCE, "{0}", SEGMENT_VARIABLES, "{1}" };
+
+ /**
+ * URL template for a single variables for an execution: runtime/executions/{0:executionId}/variables-async/{1:variableName}
+ */
+ public static final String[] URL_EXECUTION_VARIABLE_ASYNC = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_EXECUTION_RESOURCE, "{0}", SEGMENT_VARIABLES_ASYNC, "{1}" };
/**
* URL template for a single variables for an execution: runtime/executions/{0:executionId}/variables/{1:variableName}/data
@@ -345,11 +351,21 @@ public final class RestUrls {
* URL template for process instance variable collection: runtime/process-instances/{0:processInstanceId}/variables
*/
public static final String[] URL_PROCESS_INSTANCE_VARIABLE_COLLECTION = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PROCESS_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES };
+
+ /**
+ * URL template for process instance variable collection: runtime/process-instances/{0:processInstanceId}/variables-async
+ */
+ public static final String[] URL_PROCESS_INSTANCE_VARIABLE_ASYNC_COLLECTION = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PROCESS_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES_ASYNC };
/**
* URL template for a single process instance variable: runtime/process-instances /{0:processInstanceId}/variables/{1:variableName}
*/
public static final String[] URL_PROCESS_INSTANCE_VARIABLE = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PROCESS_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES, "{1}" };
+
+ /**
+ * URL template for a single process instance variable: runtime/process-instances /{0:processInstanceId}/variables-async/{1:variableName}
+ */
+ public static final String[] URL_PROCESS_INSTANCE_VARIABLE_ASYNC = { SEGMENT_RUNTIME_RESOURCES, SEGMENT_PROCESS_INSTANCE_RESOURCE, "{0}", SEGMENT_VARIABLES_ASYNC, "{1}" };
/**
* URL template for a single process instance variable data: runtime/process -instances/{0:processInstanceId}/variables/{1:variableName}/data
diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseExecutionVariableResource.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseExecutionVariableResource.java
index af76e9037fc..9613bd700db 100644
--- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseExecutionVariableResource.java
+++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseExecutionVariableResource.java
@@ -20,8 +20,6 @@
import java.util.Collections;
import java.util.Map;
-import jakarta.servlet.http.HttpServletResponse;
-
import org.apache.commons.io.IOUtils;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
@@ -40,6 +38,8 @@
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
/**
* @author Frederik Heremans
*/
@@ -99,7 +99,7 @@ protected byte[] getVariableDataByteArray(Execution execution, String variableNa
}
}
- protected RestVariable setBinaryVariable(MultipartHttpServletRequest request, Execution execution, boolean isNew) {
+ protected RestVariable setBinaryVariable(MultipartHttpServletRequest request, Execution execution, boolean isNew, boolean async) {
// Validate input and set defaults
if (request.getFileMap().size() == 0) {
@@ -157,21 +157,28 @@ protected RestVariable setBinaryVariable(MultipartHttpServletRequest request, Ex
if (variableType.equals(RestResponseFactory.BYTE_ARRAY_VARIABLE_TYPE)) {
// Use raw bytes as variable value
byte[] variableBytes = IOUtils.toByteArray(file.getInputStream());
- setVariable(execution, variableName, variableBytes, scope, isNew);
+ setVariable(execution, variableName, variableBytes, scope, isNew, async);
} else if (isSerializableVariableAllowed) {
// Try deserializing the object
ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
Object value = stream.readObject();
- setVariable(execution, variableName, value, scope, isNew);
+ setVariable(execution, variableName, value, scope, isNew, async);
stream.close();
+
} else {
throw new FlowableContentNotSupportedException("Serialized objects are not allowed");
}
- RestVariable variable = getVariableFromRequestWithoutAccessCheck(execution, variableName, scope, false);
- // We are setting the scope because the fetched variable does not have it
- variable.setVariableScope(scope);
+ RestVariable variable = null;
+
+ if (!async) {
+ variable = getVariableFromRequestWithoutAccessCheck(execution, variableName, scope, false);
+
+ // We are setting the scope because the fetched variable does not have it
+ variable.setVariableScope(scope);
+ }
+
return variable;
} catch (IOException ioe) {
@@ -182,7 +189,7 @@ protected RestVariable setBinaryVariable(MultipartHttpServletRequest request, Ex
}
- protected RestVariable setSimpleVariable(RestVariable restVariable, Execution execution, boolean isNew) {
+ protected RestVariable setSimpleVariable(RestVariable restVariable, Execution execution, boolean isNew, boolean async) {
if (restVariable.getName() == null) {
throw new FlowableIllegalArgumentException("Variable name is required");
}
@@ -194,12 +201,17 @@ protected RestVariable setSimpleVariable(RestVariable restVariable, Execution ex
}
Object actualVariableValue = restResponseFactory.getVariableValue(restVariable);
- setVariable(execution, restVariable.getName(), actualVariableValue, scope, isNew);
+ setVariable(execution, restVariable.getName(), actualVariableValue, scope, isNew, async);
- return getVariableFromRequestWithoutAccessCheck(execution, restVariable.getName(), scope, false);
+ RestVariable newRestVariable = null;
+ if (!async) {
+ newRestVariable = getVariableFromRequestWithoutAccessCheck(execution, restVariable.getName(), scope, false);
+ }
+
+ return newRestVariable;
}
- protected void setVariable(Execution execution, String name, Object value, RestVariableScope scope, boolean isNew) {
+ protected void setVariable(Execution execution, String name, Object value, RestVariableScope scope, boolean isNew, boolean async) {
// Create can only be done on new variables. Existing variables should
// be updated using PUT
boolean hasVariable = hasVariableOnScope(execution, name, scope);
@@ -220,12 +232,24 @@ protected void setVariable(Execution execution, String name, Object value, RestV
}
if (scope == RestVariableScope.LOCAL) {
- runtimeService.setVariableLocal(execution.getId(), name, value);
+ if (async) {
+ runtimeService.setVariableLocalAsync(execution.getId(), name, value);
+ } else {
+ runtimeService.setVariableLocal(execution.getId(), name, value);
+ }
} else {
+ String executionId = null;
if (execution.getParentId() != null) {
- runtimeService.setVariable(execution.getParentId(), name, value);
+ executionId = execution.getParentId();
+
+ } else {
+ executionId = execution.getId();
+ }
+
+ if (async) {
+ runtimeService.setVariableAsync(executionId, name, value);
} else {
- runtimeService.setVariable(execution.getId(), name, value);
+ runtimeService.setVariable(executionId, name, value);
}
}
}
diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseVariableCollectionResource.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseVariableCollectionResource.java
index 7b1d9c77678..c857bb83084 100644
--- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseVariableCollectionResource.java
+++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseVariableCollectionResource.java
@@ -19,9 +19,6 @@
import java.util.List;
import java.util.Map;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.rest.exception.FlowableConflictException;
import org.flowable.engine.runtime.Execution;
@@ -33,6 +30,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
/**
* @author Tijs Rademakers
*/
@@ -80,11 +80,10 @@ public void deleteAllLocalVariables(Execution execution) {
runtimeService.removeVariablesLocal(execution.getId(), currentVariables);
}
- protected Object createExecutionVariable(Execution execution, boolean override, HttpServletRequest request, HttpServletResponse response) {
-
+ protected Object createExecutionVariable(Execution execution, boolean override, boolean async, HttpServletRequest request, HttpServletResponse response) {
Object result = null;
if (request instanceof MultipartHttpServletRequest) {
- result = setBinaryVariable((MultipartHttpServletRequest) request, execution, true);
+ result = setBinaryVariable((MultipartHttpServletRequest) request, execution, true, async);
} else {
List inputVariables = new ArrayList<>();
@@ -140,27 +139,41 @@ protected Object createExecutionVariable(Execution execution, boolean override,
restApiInterceptor.createExecutionVariables(execution, variablesToSet, sharedScope);
}
- Map setVariables;
+ Map setVariables = null;
if (sharedScope == RestVariableScope.LOCAL) {
- runtimeService.setVariablesLocal(execution.getId(), variablesToSet);
- setVariables = runtimeService.getVariablesLocal(execution.getId(), variablesToSet.keySet());
+
+ if (async) {
+ runtimeService.setVariablesLocalAsync(execution.getId(), variablesToSet);
+
+ } else {
+ runtimeService.setVariablesLocal(execution.getId(), variablesToSet);
+ setVariables = runtimeService.getVariablesLocal(execution.getId(), variablesToSet.keySet());
+ }
+
} else {
if (execution.getParentId() != null) {
- // Explicitly set on parent, setting non-local variables
- // on execution itself will override local-variables if
- // exists
- runtimeService.setVariables(execution.getParentId(), variablesToSet);
- setVariables = runtimeService.getVariables(execution.getParentId(), variablesToSet.keySet());
+ // Explicitly set on parent, setting non-local variables on execution itself will override local-variables if exists
+
+ if (async) {
+ runtimeService.setVariablesAsync(execution.getParentId(), variablesToSet);
+
+ } else {
+ runtimeService.setVariables(execution.getParentId(), variablesToSet);
+ setVariables = runtimeService.getVariables(execution.getParentId(), variablesToSet.keySet());
+ }
+
} else {
// Standalone task, no global variables possible
throw new FlowableIllegalArgumentException("Cannot set global variables on execution '" + execution.getId() + "', task is not part of process.");
}
}
- for (RestVariable inputVariable : inputVariables) {
- String variableName = inputVariable.getName();
- Object variableValue = setVariables.get(variableName);
- resultVariables.add(restResponseFactory.createRestVariable(variableName, variableValue, varScope, execution.getId(), variableType, false));
+ if (!async) {
+ for (RestVariable inputVariable : inputVariables) {
+ String variableName = inputVariable.getName();
+ Object variableValue = setVariables.get(variableName);
+ resultVariables.add(restResponseFactory.createRestVariable(variableName, variableValue, varScope, execution.getId(), variableType, false));
+ }
}
}
diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableCollectionResource.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableCollectionResource.java
index 892dc77df0a..60a943c460b 100644
--- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableCollectionResource.java
+++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableCollectionResource.java
@@ -15,9 +15,6 @@
import java.util.List;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
import org.flowable.engine.runtime.Execution;
import org.flowable.rest.service.api.RestResponseFactory;
import org.flowable.rest.service.api.engine.variable.RestVariable;
@@ -39,6 +36,8 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
/**
* @author Frederik Heremans
@@ -63,7 +62,6 @@ public List getVariables(@ApiParam(name = "executionId") @PathVari
return processVariables(execution, scope);
}
- // FIXME OASv3 to solve Multiple Endpoint issue
@ApiOperation(value = "Update variables on an execution", tags = { "Executions" }, nickname = "createOrUpdateExecutionVariable",
notes = "This endpoint can be used in 2 ways: By passing a JSON Body (array of RestVariable) or by passing a multipart/form-data Object.\n"
+ "Any number of variables can be passed into the request body array.\n"
@@ -87,10 +85,34 @@ public List getVariables(@ApiParam(name = "executionId") @PathVari
public Object createOrUpdateExecutionVariable(@ApiParam(name = "executionId") @PathVariable String executionId, HttpServletRequest request, HttpServletResponse response) {
Execution execution = getExecutionFromRequestWithoutAccessCheck(executionId);
- return createExecutionVariable(execution, true, request, response);
+ return createExecutionVariable(execution, true, false, request, response);
+ }
+
+ @ApiOperation(value = "Update variables on an execution asynchronously", tags = { "Executions" }, nickname = "createOrUpdateExecutionVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (array of RestVariable) or by passing a multipart/form-data Object.\n"
+ + "Any number of variables can be passed into the request body array.\n"
+ + "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.service.api.engine.variable.RestVariable", value = "Update a task variable", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " }"),
+ @ApiImplicitParam(name = "name", value = "Required name of the variable", dataType = "string", paramType = "form", example = "Simple content item"),
+ @ApiImplicitParam(name = "type", value = "Type of variable that is updated. If omitted, reverts to raw JSON-value type (string, boolean, integer or double)",dataType = "string", paramType = "form", example = "integer"),
+ @ApiImplicitParam(name = "scope",value = "Scope of variable to be returned. When local, only task-local variable value is returned. When global, only variable value from the task’s parent execution-hierarchy are returned. When the parameter is omitted, a local variable will be returned if it exists, otherwise a global variable..", dataType = "string", paramType = "form", example = "local")
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to update the variables has been created."),
+ @ApiResponse(code = 400, message = "Indicates the request body is incomplete or contains illegal values. The status description contains additional information about the error."),
+ })
+ @PutMapping(value = "/runtime/executions/{executionId}/variables-async", produces = "application/json", consumes = {"application/json", "multipart/form-data"})
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void createOrUpdateExecutionVariableAsync(@ApiParam(name = "executionId") @PathVariable String executionId, HttpServletRequest request, HttpServletResponse response) {
+ Execution execution = getExecutionFromRequestWithoutAccessCheck(executionId);
+ createExecutionVariable(execution, true, true, request, response);
}
- // FIXME OASv3 to solve Multiple Endpoint issue
@ApiOperation(value = "Create variables on an execution", tags = { "Executions" }, nickname = "createExecutionVariable",
notes = "This endpoint can be used in 2 ways: By passing a JSON Body (array of RestVariable) or by passing a multipart/form-data Object.\n"
+ "Any number of variables can be passed into the request body array.\n"
@@ -116,7 +138,34 @@ public Object createOrUpdateExecutionVariable(@ApiParam(name = "executionId") @P
public Object createExecutionVariable(@ApiParam(name = "executionId") @PathVariable String executionId, HttpServletRequest request, HttpServletResponse response) {
Execution execution = getExecutionFromRequestWithoutAccessCheck(executionId);
- return createExecutionVariable(execution, false, request, response);
+ return createExecutionVariable(execution, false, false, request, response);
+ }
+
+ @ApiOperation(value = "Create variables on an execution asynchronously", tags = { "Executions" }, nickname = "createExecutionVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (array of RestVariable) or by passing a multipart/form-data Object.\n"
+ + "Any number of variables can be passed into the request body array.\n"
+ + "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.service.api.engine.variable.RestVariable", value = "Update a task variable", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " }"),
+ @ApiImplicitParam(name = "name", value = "Required name of the variable", dataType = "string", paramType = "form", example = "Simple content item"),
+ @ApiImplicitParam(name = "type", value = "Type of variable that is updated. If omitted, reverts to raw JSON-value type (string, boolean, integer or double)",dataType = "string", paramType = "form", example = "integer"),
+ @ApiImplicitParam(name = "scope",value = "Scope of variable to be returned. When local, only task-local variable value is returned. When global, only variable value from the task’s parent execution-hierarchy are returned. When the parameter is omitted, a local variable will be returned if it exists, otherwise a global variable..", dataType = "string", paramType = "form", example = "local")
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to create the variables has been created."),
+ @ApiResponse(code = 400, message = "Indicates the request body is incomplete or contains illegal values. The status description contains additional information about the error."),
+ @ApiResponse(code = 409, message = "Indicates the execution already contains a variable with the given name. Use the update-method instead.")
+
+ })
+ @PostMapping(value = "/runtime/executions/{executionId}/variables-async", produces = "application/json", consumes = {"application/json", "multipart/form-data"})
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void createExecutionVariableAsync(@ApiParam(name = "executionId") @PathVariable String executionId, HttpServletRequest request, HttpServletResponse response) {
+ Execution execution = getExecutionFromRequestWithoutAccessCheck(executionId);
+ createExecutionVariable(execution, false, true, request, response);
}
@ApiOperation(value = "Delete all variables for an execution", tags = { "Executions" }, code = 204)
diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableResource.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableResource.java
index 44f9f6a90c4..d9c4d0424ea 100644
--- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableResource.java
+++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableResource.java
@@ -15,8 +15,6 @@
import java.util.Collections;
-import jakarta.servlet.http.HttpServletRequest;
-
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
@@ -46,6 +44,7 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
+import jakarta.servlet.http.HttpServletRequest;
/**
* @author Frederik Heremans
@@ -75,7 +74,6 @@ public RestVariable getVariable(@ApiParam(name = "executionId") @PathVariable("e
return getVariableFromRequest(execution, variableName, scope, false);
}
- // FIXME OASv3 to solve Multiple Endpoint issue
@ApiOperation(value = "Update a variable on an execution", tags = { "Executions" }, nickname = "updateExecutionVariable",
notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
@@ -103,7 +101,7 @@ public RestVariable updateVariable(@ApiParam(name = "executionId") @PathVariable
RestVariable result = null;
if (request instanceof MultipartHttpServletRequest) {
- result = setBinaryVariable((MultipartHttpServletRequest) request, execution, false);
+ result = setBinaryVariable((MultipartHttpServletRequest) request, execution, false, false);
if (!result.getName().equals(variableName)) {
throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
@@ -126,10 +124,58 @@ public RestVariable updateVariable(@ApiParam(name = "executionId") @PathVariable
throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
}
- result = setSimpleVariable(restVariable, execution, false);
+ result = setSimpleVariable(restVariable, execution, false, false);
}
return result;
}
+
+ @ApiOperation(value = "Update a variable on an execution asynchronously", tags = { "Executions" }, nickname = "updateExecutionVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ + "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.service.api.engine.variable.RestVariable", value = "Update a variable on an execution", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " \"scope\":\"global\"\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "intProcVar"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ @ApiImplicitParam(name = "scope", dataType = "string", paramType = "form", example = "global"),
+
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 200, message = "Indicates both the process instance and variable were found and variable is updated."),
+ @ApiResponse(code = 404, message = "Indicates the process instance does not have a variable with the given name. Status description contains additional information about the error.")
+ })
+ @PutMapping(value = "/runtime/executions/{executionId}/variables-async/{variableName}", consumes = {"application/json", "multipart/form-data"})
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void updateVariableAsync(@ApiParam(name = "executionId") @PathVariable("executionId") String executionId, @ApiParam(name = "variableName") @PathVariable("variableName") String variableName, HttpServletRequest request) {
+ Execution execution = getExecutionFromRequestWithoutAccessCheck(executionId);
+
+ if (request instanceof MultipartHttpServletRequest) {
+ setBinaryVariable((MultipartHttpServletRequest) request, execution, false, true);
+
+ } else {
+ RestVariable restVariable = null;
+
+ try {
+ restVariable = objectMapper.readValue(request.getInputStream(), RestVariable.class);
+ } catch (Exception e) {
+ throw new FlowableIllegalArgumentException("Error converting request body to RestVariable instance", e);
+ }
+
+ if (restVariable == null) {
+ throw new FlowableException("Invalid body was supplied");
+ }
+ if (!restVariable.getName().equals(variableName)) {
+ throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
+ }
+
+ setSimpleVariable(restVariable, execution, false, true);
+ }
+ }
@ApiOperation(value = "Delete a variable for an execution", tags = { "Executions" }, nickname = "deletedExecutionVariable", code = 204)
@ApiResponses(value = {
diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceVariableCollectionResource.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceVariableCollectionResource.java
index a5c7db79d22..1f5cf5ab4c1 100644
--- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceVariableCollectionResource.java
+++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceVariableCollectionResource.java
@@ -16,9 +16,6 @@
import java.util.List;
import java.util.Map;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
import org.flowable.engine.runtime.Execution;
import org.flowable.rest.service.api.RestResponseFactory;
import org.flowable.rest.service.api.engine.variable.RestVariable;
@@ -41,6 +38,8 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
/**
* @author Tijs Rademakers
@@ -66,7 +65,6 @@ public List getVariables(@ApiParam(name = "processInstanceId") @Pa
return processVariables(execution, scope);
}
- // FIXME OASv3 to solve Multiple Endpoint issue
@ApiOperation(value = "Update a multiple/single (non)binary variable on a process instance", tags = { "Process Instance Variables" }, nickname = "createOrUpdateProcessVariable",
notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable or an array of RestVariable) or by passing a multipart/form-data Object.\n"
+ "Nonexistent variables are created on the process-instance and existing ones are overridden without any error.\n"
@@ -94,10 +92,37 @@ public List getVariables(@ApiParam(name = "processInstanceId") @Pa
public Object createOrUpdateExecutionVariable(@ApiParam(name = "processInstanceId") @PathVariable String processInstanceId, HttpServletRequest request, HttpServletResponse response) {
Execution execution = getExecutionFromRequestWithoutAccessCheck(processInstanceId);
- return createExecutionVariable(execution, true, request, response);
+ return createExecutionVariable(execution, true, false, request, response);
+ }
+
+ @ApiOperation(value = "Update multiple/single (non)binary variables on a process instance asynchronously", tags = { "Process Instance Variables" }, nickname = "createOrUpdateProcessVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable or an array of RestVariable) or by passing a multipart/form-data Object.\n"
+ + "Nonexistent variables are created on the process-instance and existing ones are overridden without any error.\n"
+ + "Any number of variables can be passed into the request body array.\n"
+ + "Note that scope is ignored, only local variables can be set in a process instance.\n"
+ + "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.service.api.engine.variable.RestVariable", value = "Create a variable on a process instance", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "Simple content item"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to create or update the variables was created."),
+ @ApiResponse(code = 400, message = "Indicates the request body is incomplete or contains illegal values. The status description contains additional information about the error."),
+ @ApiResponse(code = 415, message = "Indicates the serializable data contains an object for which no class is present in the JVM running the Flowable engine and therefore cannot be deserialized.")
+
+ })
+ @PutMapping(value = "/runtime/process-instances/{processInstanceId}/variables-async", consumes = {"application/json", "multipart/form-data"})
+ public void createOrUpdateExecutionVariableAsync(@ApiParam(name = "processInstanceId") @PathVariable String processInstanceId, HttpServletRequest request, HttpServletResponse response) {
+ Execution execution = getExecutionFromRequestWithoutAccessCheck(processInstanceId);
+ createExecutionVariable(execution, true, true, request, response);
}
- // FIXME OASv3 to solve Multiple Endpoint issue
@ApiOperation(value = "Create variables or new binary variable on a process instance", tags = { "Process Instance Variables" }, nickname = "createProcessInstanceVariable",
notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable or an array of RestVariable) or by passing a multipart/form-data Object.\n"
+ "Nonexistent variables are created on the process-instance and existing ones are overridden without any error.\n"
@@ -125,7 +150,35 @@ public Object createOrUpdateExecutionVariable(@ApiParam(name = "processInstanceI
public Object createExecutionVariable(@ApiParam(name = "processInstanceId") @PathVariable String processInstanceId, HttpServletRequest request, HttpServletResponse response) {
Execution execution = getExecutionFromRequestWithoutAccessCheck(processInstanceId);
- return createExecutionVariable(execution, false, request, response);
+ return createExecutionVariable(execution, false, false, request, response);
+ }
+
+ @ApiOperation(value = "Create variables or new binary variable on a process instance asynchronously", tags = { "Process Instance Variables" }, nickname = "createProcessInstanceVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable or an array of RestVariable) or by passing a multipart/form-data Object.\n"
+ + "Nonexistent variables are created on the process-instance and existing ones are overridden without any error.\n"
+ + "Any number of variables can be passed into the request body array.\n"
+ + "Note that scope is ignored, only local variables can be set in a process instance.\n"
+ + "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.service.api.engine.variable.RestVariable", value = "Create a variable on a process instance", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "Simple content item"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to create the variables was created."),
+ @ApiResponse(code = 400, message = "Indicates the request body is incomplete or contains illegal values. The status description contains additional information about the error."),
+ @ApiResponse(code = 409, message = "Indicates the process instance was found but already contains a variable with the given name (only thrown when POST method is used). Use the update-method instead."),
+
+ })
+ @PostMapping(value = "/runtime/process-instances/{processInstanceId}/variables-async", consumes = {"application/json", "multipart/form-data"})
+ public void createExecutionVariableAsync(@ApiParam(name = "processInstanceId") @PathVariable String processInstanceId, HttpServletRequest request, HttpServletResponse response) {
+ Execution execution = getExecutionFromRequestWithoutAccessCheck(processInstanceId);
+ createExecutionVariable(execution, false, true, request, response);
}
// FIXME Documentation
diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceVariableResource.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceVariableResource.java
index ff71f9e8af6..67756587064 100644
--- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceVariableResource.java
+++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceVariableResource.java
@@ -15,8 +15,6 @@
import java.util.Collections;
-import jakarta.servlet.http.HttpServletRequest;
-
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
@@ -46,6 +44,7 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
+import jakarta.servlet.http.HttpServletRequest;
/**
* @author Frederik Heremans
@@ -74,7 +73,6 @@ public RestVariable getVariable(@ApiParam(name = "processInstanceId") @PathVaria
return getVariableFromRequest(execution, variableName, scope, false);
}
- // FIXME OASv3 to solve Multiple Endpoint issue
@ApiOperation(value = "Update a single variable on a process instance", tags = { "Process Instance Variables" }, nickname = "updateProcessInstanceVariable",
notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ "Nonexistent variables are created on the process-instance and existing ones are overridden without any error.\n"
@@ -102,7 +100,7 @@ public RestVariable updateVariable(@ApiParam(name = "processInstanceId") @PathVa
RestVariable result = null;
if (request instanceof MultipartHttpServletRequest) {
- result = setBinaryVariable((MultipartHttpServletRequest) request, execution, false);
+ result = setBinaryVariable((MultipartHttpServletRequest) request, execution, false, false);
if (!result.getName().equals(variableName)) {
throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
@@ -123,10 +121,58 @@ public RestVariable updateVariable(@ApiParam(name = "processInstanceId") @PathVa
throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
}
- result = setSimpleVariable(restVariable, execution, false);
+ result = setSimpleVariable(restVariable, execution, false, false);
}
return result;
}
+
+ @ApiOperation(value = "Update a single variable on a process instance asynchronously", tags = { "Process Instance Variables" }, nickname = "updateProcessInstanceVariableAsync",
+ notes = "This endpoint can be used in 2 ways: By passing a JSON Body (RestVariable) or by passing a multipart/form-data Object.\n"
+ + "Nonexistent variables are created on the process-instance and existing ones are overridden without any error.\n"
+ + "Note that scope is ignored, only local variables can be set in a process instance.\n"
+ + "NB: Swagger V2 specification does not support this use case that is why this endpoint might be buggy/incomplete if used with other tools.")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "body", type = "org.flowable.rest.service.api.engine.variable.RestVariable", value = "Create a variable on a process instance", paramType = "body", example = "{\n" +
+ " \"name\":\"intProcVar\"\n" +
+ " \"type\":\"integer\"\n" +
+ " \"value\":123,\n" +
+ " }"),
+ @ApiImplicitParam(name = "file", dataType = "file", paramType = "form"),
+ @ApiImplicitParam(name = "name", dataType = "string", paramType = "form", example = "Simple content item"),
+ @ApiImplicitParam(name = "type", dataType = "string", paramType = "form", example = "integer"),
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Indicates the job to update the variable has been created."),
+ @ApiResponse(code = 404, message = "Indicates the process instance does not have a variable with the given name. Status description contains additional information about the error.")
+ })
+ @PutMapping(value = "/runtime/process-instances/{processInstanceId}/variables-async/{variableName}", consumes = {"application/json", "multipart/form-data"})
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void updateVariableAsync(@ApiParam(name = "processInstanceId") @PathVariable("processInstanceId") String processInstanceId, @ApiParam(name = "variableName") @PathVariable("variableName") String variableName,
+ HttpServletRequest request) {
+
+ Execution execution = getExecutionFromRequestWithoutAccessCheck(processInstanceId);
+
+ if (request instanceof MultipartHttpServletRequest) {
+ setBinaryVariable((MultipartHttpServletRequest) request, execution, false, true);
+
+ } else {
+ RestVariable restVariable = null;
+ try {
+ restVariable = objectMapper.readValue(request.getInputStream(), RestVariable.class);
+ } catch (Exception e) {
+ throw new FlowableIllegalArgumentException("request body could not be transformed to a RestVariable instance.", e);
+ }
+
+ if (restVariable == null) {
+ throw new FlowableException("Invalid body was supplied");
+ }
+ if (!restVariable.getName().equals(variableName)) {
+ throw new FlowableIllegalArgumentException("Variable name in the body should be equal to the name used in the requested URL.");
+ }
+
+ setSimpleVariable(restVariable, execution, false, true);
+ }
+ }
// FIXME Documentation
@ApiOperation(value = "Delete a variable", tags = { "Process Instance Variables" }, nickname = "deleteProcessInstanceVariable", code = 204)
diff --git a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ExecutionVariableResourceTest.java b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ExecutionVariableResourceTest.java
index c001af0baef..94e69927d09 100644
--- a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ExecutionVariableResourceTest.java
+++ b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ExecutionVariableResourceTest.java
@@ -34,6 +34,7 @@
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.test.Deployment;
+import org.flowable.job.api.Job;
import org.flowable.rest.service.BaseSpringRestTestCase;
import org.flowable.rest.service.HttpMultipartHelper;
import org.flowable.rest.service.api.RestUrls;
@@ -385,4 +386,74 @@ public void testUpdateBinaryExecutionVariable() throws Exception {
assertThat(variableValue).isInstanceOf(byte[].class);
assertThat(new String((byte[]) variableValue)).isEqualTo("This is binary content");
}
+
+ @Test
+ @Deployment(resources = { "org/flowable/rest/service/api/runtime/ExecutionResourceTest.process-with-subprocess.bpmn20.xml" })
+ public void testUpdateExecutionVariableAsync() throws Exception {
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("processOne", Collections.singletonMap("overlappingVariable", (Object) "processValue"));
+ runtimeService.setVariable(processInstance.getId(), "myVar", "processValue");
+
+ Execution childExecution = runtimeService.createExecutionQuery().parentId(processInstance.getId()).singleResult();
+ assertThat(childExecution).isNotNull();
+ runtimeService.setVariableLocal(childExecution.getId(), "myVar", "childValue");
+
+ // Update variable local
+ ObjectNode requestNode = objectMapper.createObjectNode();
+ requestNode.put("name", "myVar");
+ requestNode.put("value", "updatedValue");
+ requestNode.put("type", "string");
+
+ HttpPut httpPut = new HttpPut(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_EXECUTION_VARIABLE_ASYNC, childExecution.getId(), "myVar"));
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ CloseableHttpResponse response = executeRequest(httpPut, HttpStatus.SC_NO_CONTENT);
+ closeResponse(response);
+
+ assertThat(runtimeService.getVariable(processInstance.getId(), "myVar")).isEqualTo("processValue");
+ assertThat(runtimeService.getVariableLocal(childExecution.getId(), "myVar")).isEqualTo("childValue");
+
+ Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ // Global value should be unaffected
+ assertThat(runtimeService.getVariable(processInstance.getId(), "myVar")).isEqualTo("processValue");
+ assertThat(runtimeService.getVariableLocal(childExecution.getId(), "myVar")).isEqualTo("updatedValue");
+
+ // Update variable global
+ requestNode = objectMapper.createObjectNode();
+ requestNode.put("name", "myVar");
+ requestNode.put("value", "updatedValueGlobal");
+ requestNode.put("type", "string");
+ requestNode.put("scope", "global");
+
+ httpPut = new HttpPut(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_EXECUTION_VARIABLE_ASYNC, childExecution.getId(), "myVar"));
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ response = executeRequest(httpPut, HttpStatus.SC_NO_CONTENT);
+ closeResponse(response);
+
+ assertThat(runtimeService.getVariable(processInstance.getId(), "myVar")).isEqualTo("processValue");
+ assertThat(runtimeService.getVariableLocal(childExecution.getId(), "myVar")).isEqualTo("updatedValue");
+
+ job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ // Local value should be unaffected
+ assertThat(runtimeService.getVariable(processInstance.getId(), "myVar")).isEqualTo("updatedValueGlobal");
+ assertThat(runtimeService.getVariableLocal(childExecution.getId(), "myVar")).isEqualTo("updatedValue");
+
+ requestNode.put("name", "unexistingVariable");
+
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ response = executeRequest(httpPut, HttpStatus.SC_BAD_REQUEST);
+ closeResponse(response);
+
+ httpPut = new HttpPut(
+ SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_EXECUTION_VARIABLE_ASYNC, childExecution.getId(), "unexistingVariable"));
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ response = executeRequest(httpPut, HttpStatus.SC_NOT_FOUND);
+ closeResponse(response);
+ }
}
diff --git a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.java b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.java
index f4265ec0ca2..2a773a75b19 100644
--- a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.java
+++ b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.java
@@ -37,6 +37,7 @@
import org.apache.http.entity.StringEntity;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.test.Deployment;
+import org.flowable.job.api.Job;
import org.flowable.rest.service.BaseSpringRestTestCase;
import org.flowable.rest.service.HttpMultipartHelper;
import org.flowable.rest.service.api.RestUrls;
@@ -456,4 +457,45 @@ public void testUpdateBinaryProcessVariable() throws Exception {
assertThat(variableValue).isInstanceOf(byte[].class);
assertThat(new String((byte[]) variableValue)).isEqualTo("This is binary content");
}
+
+ @Test
+ @Deployment(resources = { "org/flowable/rest/service/api/runtime/ProcessInstanceVariableResourceTest.testProcess.bpmn20.xml" })
+ public void testUpdateProcessVariableAsync() throws Exception {
+ ProcessInstance processInstance = runtimeService
+ .startProcessInstanceByKey("oneTaskProcess", Collections.singletonMap("overlappingVariable", (Object) "processValue"));
+ runtimeService.setVariable(processInstance.getId(), "myVar", "value");
+
+ // Update variable
+ ObjectNode requestNode = objectMapper.createObjectNode();
+ requestNode.put("name", "myVar");
+ requestNode.put("value", "updatedValue");
+ requestNode.put("type", "string");
+
+ HttpPut httpPut = new HttpPut(
+ SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_INSTANCE_VARIABLE_ASYNC, processInstance.getId(), "myVar"));
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ CloseableHttpResponse response = executeRequest(httpPut, HttpStatus.SC_NO_CONTENT);
+ closeResponse(response);
+
+ assertThat(runtimeService.getVariable(processInstance.getId(), "myVar").equals("value"));
+
+ Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ assertThat(runtimeService.getVariable(processInstance.getId(), "myVar").equals("updatedValue"));
+
+ // Try updating with mismatch between URL and body variableName
+ requestNode.put("name", "unexistingVariable");
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ closeResponse(executeRequest(httpPut, HttpStatus.SC_BAD_REQUEST));
+
+ // Try updating unexisting property
+ requestNode.put("name", "unexistingVariable");
+ httpPut = new HttpPut(
+ SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_INSTANCE_VARIABLE_ASYNC, processInstance.getId(), "unexistingVariable"));
+ httpPut.setEntity(new StringEntity(requestNode.toString()));
+ closeResponse(executeRequest(httpPut, HttpStatus.SC_NOT_FOUND));
+ }
}
diff --git a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariablesCollectionResourceTest.java b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariablesCollectionResourceTest.java
index 7eda9212ffc..8c9775dd25d 100644
--- a/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariablesCollectionResourceTest.java
+++ b/modules/flowable-rest/src/test/java/org/flowable/rest/service/api/runtime/ProcessInstanceVariablesCollectionResourceTest.java
@@ -34,6 +34,7 @@
import org.apache.http.entity.StringEntity;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.test.Deployment;
+import org.flowable.job.api.Job;
import org.flowable.rest.service.BaseSpringRestTestCase;
import org.flowable.rest.service.HttpMultipartHelper;
import org.flowable.rest.service.api.RestUrls;
@@ -420,6 +421,148 @@ public void testCreateMultipleProcessVariablesWithOverride() throws Exception {
entry("stringVariable2", "another string value")
);
}
+
+ @Test
+ @Deployment(resources = { "org/flowable/rest/service/api/runtime/ProcessInstanceVariablesCollectionResourceTest.testProcess.bpmn20.xml" })
+ public void testCreateSingleProcessInstanceVariableAsync() throws Exception {
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess");
+
+ ArrayNode requestNode = objectMapper.createArrayNode();
+ ObjectNode variableNode = requestNode.addObject();
+ variableNode.put("name", "myVariable");
+ variableNode.put("value", "simple string value");
+ variableNode.put("type", "string");
+
+ // Create a new local variable
+ HttpPost httpPost = new HttpPost(
+ SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_INSTANCE_VARIABLE_ASYNC_COLLECTION, processInstance.getId()));
+ httpPost.setEntity(new StringEntity(requestNode.toString()));
+ CloseableHttpResponse response = executeRequest(httpPost, HttpStatus.SC_CREATED);
+ closeResponse(response);
+
+ assertThat(runtimeService.hasVariable(processInstance.getId(), "myVariable")).isFalse();
+
+ Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ assertThat(runtimeService.hasVariable(processInstance.getId(), "myVariable")).isTrue();
+ assertThat(runtimeService.getVariableLocal(processInstance.getId(), "myVariable")).isEqualTo("simple string value");
+ }
+
+ @Test
+ @Deployment(resources = { "org/flowable/rest/service/api/runtime/ProcessInstanceVariablesCollectionResourceTest.testProcess.bpmn20.xml" })
+ public void testCreateSingleBinaryProcessVariableAsync() throws Exception {
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess");
+
+ InputStream binaryContent = new ByteArrayInputStream("This is binary content".getBytes());
+
+ // Add name, type and scope
+ Map additionalFields = new HashMap<>();
+ additionalFields.put("name", "binaryVariable");
+ additionalFields.put("type", "binary");
+
+ HttpPost httpPost = new HttpPost(
+ SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_INSTANCE_VARIABLE_ASYNC_COLLECTION, processInstance.getId()));
+ httpPost.setEntity(HttpMultipartHelper.getMultiPartEntity("value", "application/octet-stream", binaryContent, additionalFields));
+ CloseableHttpResponse response = executeBinaryRequest(httpPost, HttpStatus.SC_CREATED);
+ closeResponse(response);
+
+ assertThat(runtimeService.hasVariable(processInstance.getId(), "binaryVariable")).isFalse();
+
+ Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ // Check actual value of variable in engine
+ Object variableValue = runtimeService.getVariable(processInstance.getId(), "binaryVariable");
+ assertThat(variableValue).isInstanceOf(byte[].class);
+ assertThat(new String((byte[]) variableValue)).isEqualTo("This is binary content");
+ }
+
+ @Test
+ @Deployment(resources = { "org/flowable/rest/service/api/runtime/ProcessInstanceVariablesCollectionResourceTest.testProcess.bpmn20.xml" })
+ public void testCreateMultipleProcessVariablesAsync() throws Exception {
+
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess");
+
+ ArrayNode requestNode = objectMapper.createArrayNode();
+
+ // String variable
+ ObjectNode stringVarNode = requestNode.addObject();
+ stringVarNode.put("name", "stringVariable");
+ stringVarNode.put("value", "simple string value");
+ stringVarNode.put("type", "string");
+
+ // Integer
+ ObjectNode integerVarNode = requestNode.addObject();
+ integerVarNode.put("name", "integerVariable");
+ integerVarNode.put("value", 1234);
+ integerVarNode.put("type", "integer");
+
+ // Short
+ ObjectNode shortVarNode = requestNode.addObject();
+ shortVarNode.put("name", "shortVariable");
+ shortVarNode.put("value", 123);
+ shortVarNode.put("type", "short");
+
+ // Long
+ ObjectNode longVarNode = requestNode.addObject();
+ longVarNode.put("name", "longVariable");
+ longVarNode.put("value", 4567890L);
+ longVarNode.put("type", "long");
+
+ // Double
+ ObjectNode doubleVarNode = requestNode.addObject();
+ doubleVarNode.put("name", "doubleVariable");
+ doubleVarNode.put("value", 123.456);
+ doubleVarNode.put("type", "double");
+
+ // Boolean
+ ObjectNode booleanVarNode = requestNode.addObject();
+ booleanVarNode.put("name", "booleanVariable");
+ booleanVarNode.put("value", Boolean.TRUE);
+ booleanVarNode.put("type", "boolean");
+
+ // Date
+ Calendar varCal = Calendar.getInstance();
+ String isoString = getISODateString(varCal.getTime());
+
+ ObjectNode dateVarNode = requestNode.addObject();
+ dateVarNode.put("name", "dateVariable");
+ dateVarNode.put("value", isoString);
+ dateVarNode.put("type", "date");
+
+ // Create local variables with a single request
+ HttpPost httpPost = new HttpPost(
+ SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_INSTANCE_VARIABLE_ASYNC_COLLECTION, processInstance.getId()));
+ httpPost.setEntity(new StringEntity(requestNode.toString()));
+ CloseableHttpResponse response = executeRequest(httpPost, HttpStatus.SC_CREATED);
+ closeResponse(response);
+
+ assertThat(runtimeService.hasVariable(processInstance.getId(), "stringVariable")).isFalse();
+ assertThat(runtimeService.hasVariable(processInstance.getId(), "integerVariable")).isFalse();
+
+ Job job = managementService.createJobQuery().processInstanceId(processInstance.getId()).singleResult();
+ assertThat(job).isNotNull();
+
+ managementService.executeJob(job.getId());
+
+ // Check if engine has correct variables set
+ Map variables = runtimeService.getVariables(processInstance.getId());
+ assertThat(variables)
+ .containsOnly(
+ entry("stringVariable", "simple string value"),
+ entry("integerVariable", 1234),
+ entry("shortVariable", (short) 123),
+ entry("longVariable", 4567890L),
+ entry("doubleVariable", 123.456),
+ entry("booleanVariable", true),
+ entry("dateVariable", dateFormat.parse(isoString))
+ );
+ }
/**
* Test deleting all process variables. DELETE runtime/process-instance/{processInstanceId}/variables