提交 3839ddca 编写于 作者: F Frederik Heremans

Added creating of (non-binary) REST variables with all type-support

上级 bf13fcd7
......@@ -18,6 +18,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.impl.bpmn.deployer.BpmnDeployer;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.Deployment;
......@@ -180,6 +181,30 @@ public class RestResponseFactory {
return restVar;
}
public Object getVariableValue(RestVariable restVariable) {
Object value = null;
if(restVariable.getType() != null) {
// Try locating a converter if the type has been specified
RestVariableConverter converter = null;
for(RestVariableConverter conv : variableConverters) {
if(conv.getRestTypeName().equals(restVariable.getType())) {
converter = conv;
break;
}
}
if(converter == null) {
throw new ActivitiIllegalArgumentException("Variable '" + restVariable.getName() + "' has unsupported type: '" + restVariable.getType() + "'.");
}
value = converter.getVariableValue(restVariable);
} else {
// Revert to type determined by REST-to-Java mapping when no explicit type has been provided
value = restVariable.getValue();
}
return value;
}
/**
* Called once when the converters need to be initialized. Override of custom conversion
* needs to be done between java and rest.
......@@ -193,4 +218,5 @@ public class RestResponseFactory {
variableConverters.add(new BooleanRestVariableConverter());
variableConverters.add(new DateRestVariableConverter());
}
}
......@@ -33,7 +33,7 @@ public class RestVariable {
private String name;
private String type;
private RestVariableScope variableScope = RestVariableScope.GLOBAL;
private RestVariableScope variableScope;
private Object value;
private String valueUrl;
......@@ -56,9 +56,6 @@ public class RestVariable {
}
public void setVariableScope(RestVariableScope variableScope) {
this.variableScope = variableScope;
if(variableScope == null) {
variableScope = RestVariableScope.GLOBAL;
}
}
public Object getValue() {
return value;
......
......@@ -75,9 +75,27 @@ public class BaseTaskVariableResource extends SecuredResource {
}
if(!variableFound) {
throw new ActivitiObjectNotFoundException("Task '" + taskId + "' doesn't have a variable with name: '" + variableName + "'.", VariableInstanceEntity.class);
throw new ActivitiObjectNotFoundException("Task '" + taskId + "' doesn't have a variable with name: '" + variableName + "'.", VariableInstanceEntity.class);
} else {
return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory()
.createRestVariable(this, variableName, value, variableScope, taskId, null, includeBinary);
}
return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory()
.createRestVariable(this, variableName, value, variableScope, taskId, null, includeBinary);
}
protected boolean hasVariableOnScope(String taskId, String variableName, RestVariableScope scope) {
boolean variableFound = false;
if(scope == RestVariableScope.GLOBAL) {
Task task = ActivitiUtil.getTaskService().createTaskQuery().taskId(taskId).singleResult();
if(task.getExecutionId() != null && ActivitiUtil.getRuntimeService().hasVariable(task.getExecutionId(), variableName)) {
variableFound = true;
}
} else if(scope == RestVariableScope.LOCAL) {
if(ActivitiUtil.getTaskService().hasVariableLocal(taskId, variableName)) {
variableFound = true;
}
}
return variableFound;
}
}
......@@ -22,17 +22,19 @@ import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.task.Task;
import org.activiti.rest.api.ActivitiUtil;
import org.activiti.rest.api.SecuredResource;
import org.activiti.rest.api.engine.variable.RestVariable;
import org.activiti.rest.api.engine.variable.RestVariable.RestVariableScope;
import org.activiti.rest.application.ActivitiRestServicesApplication;
import org.restlet.data.Status;
import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.restlet.resource.ResourceException;
/**
* @author Frederik Heremans
*/
public class TaskVariableCollectionResource extends SecuredResource {
public class TaskVariableCollectionResource extends BaseTaskVariableResource {
@Get
public List<RestVariable> getVariables() {
......@@ -104,4 +106,51 @@ public class TaskVariableCollectionResource extends SecuredResource {
return task;
}
@Post
public RestVariable createVariable(RestVariable restVariable) {
if (authenticate() == false)
return null;
// TODO: check if request is multipart-form
if(restVariable.getName() == null) {
throw new ActivitiIllegalArgumentException("Variable name is required");
}
String taskId = getAttribute("taskId");
if(taskId == null) {
throw new ActivitiIllegalArgumentException("TaskId is required");
}
// Figure out scope, revert to local is omitted
RestVariableScope scope = restVariable.getVariableScope();
if(scope == null) {
scope = RestVariableScope.LOCAL;
}
// POST can only be done on new variables. Existing variables should be updated using PUT
if(hasVariableOnScope(taskId, restVariable.getName(), scope)) {
throw new ResourceException(new Status(Status.CLIENT_ERROR_CONFLICT.getCode(), "Variable '" + restVariable.getName() + "' is already present on task '" + taskId + "'.", null, null));
}
Object actualVariableValue = getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory()
.getVariableValue(restVariable);
if(scope == RestVariableScope.LOCAL) {
ActivitiUtil.getTaskService().setVariableLocal(taskId, restVariable.getName(), actualVariableValue);
} else {
Task task = ActivitiUtil.getTaskService().createTaskQuery().taskId(taskId).singleResult();
if(task.getExecutionId() != null) {
// Explicitly set on execution, setting non-local variable on task will override local-variable if exists
ActivitiUtil.getRuntimeService().setVariable(task.getExecutionId(), restVariable.getName(), actualVariableValue);
} else {
// Standalone task, no global variables possible
throw new ActivitiIllegalArgumentException("Cannot set global variable '" + restVariable.getName() + "' on task '" + taskId +"', task is not part of process.");
}
}
// Return created-status and variable representation
setStatus(Status.SUCCESS_CREATED);
return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory()
.createRestVariable(this, restVariable.getName(), actualVariableValue, scope, taskId, null, false);
}
}
......@@ -15,6 +15,7 @@ package org.activiti.rest.api.runtime;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.runtime.ProcessInstance;
......@@ -23,9 +24,11 @@ import org.activiti.engine.test.Deployment;
import org.activiti.rest.BaseRestTestCase;
import org.activiti.rest.api.RestUrls;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ObjectNode;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;
/**
......@@ -130,4 +133,212 @@ public class TaskVariablesCollectionResourceTest extends BaseRestTestCase {
}
assertTrue(foundOverlapping);
}
/**
* Test creating a single task variable.
* POST runtime/tasks/{taskId}/variables
*/
@Deployment
public void testCreateSingleTaskVariable() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess");
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
ObjectNode requestNode = objectMapper.createObjectNode();
requestNode.put("name", "myVariable");
requestNode.put("value", "simple string value");
requestNode.put("scope", "local");
requestNode.put("type", "string");
// Create a new local variable
ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLES_COLLECTION, task.getId()));
Representation response = client.post(requestNode);
assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus());
JsonNode responseNode = objectMapper.readTree(response.getStream());
assertNotNull(responseNode);
assertEquals("myVariable", responseNode.get("name").asText());
assertEquals("simple string value", responseNode.get("value").asText());
assertEquals("local", responseNode.get("scope").asText());
assertEquals("string", responseNode.get("type").asText());
assertNull(responseNode.get("valueUrl"));
assertTrue(taskService.hasVariableLocal(task.getId(), "myVariable"));
assertEquals("simple string value", taskService.getVariableLocal(task.getId(), "myVariable"));
response.release();
// Create a new global variable
requestNode.put("name", "myVariable");
requestNode.put("value", "Another simple string value");
requestNode.put("scope", "global");
requestNode.put("type", "string");
client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLES_COLLECTION, task.getId()));
response = client.post(requestNode);
assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus());
responseNode = objectMapper.readTree(response.getStream());
assertNotNull(responseNode);
assertEquals("myVariable", responseNode.get("name").asText());
assertEquals("Another simple string value", responseNode.get("value").asText());
assertEquals("global", responseNode.get("scope").asText());
assertEquals("string", responseNode.get("type").asText());
assertNull(responseNode.get("valueUrl"));
assertTrue(runtimeService.hasVariable(task.getExecutionId(), "myVariable"));
assertEquals("Another simple string value", runtimeService.getVariableLocal(task.getExecutionId(), "myVariable"));
// Create a new scope-less variable, which defaults to local variables
requestNode = objectMapper.createObjectNode();
requestNode.put("name", "scopelessVariable");
requestNode.put("value", "simple string value");
requestNode.put("type", "string");
client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLES_COLLECTION, task.getId()));
response = client.post(requestNode);
assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus());
responseNode = objectMapper.readTree(response.getStream());
assertNotNull(responseNode);
assertEquals("scopelessVariable", responseNode.get("name").asText());
assertEquals("simple string value", responseNode.get("value").asText());
assertEquals("local", responseNode.get("scope").asText());
assertEquals("string", responseNode.get("type").asText());
assertNull(responseNode.get("valueUrl"));
assertTrue(taskService.hasVariableLocal(task.getId(), "scopelessVariable"));
assertEquals("simple string value", taskService.getVariableLocal(task.getId(), "scopelessVariable"));
response.release();
}
/**
* Test creating a single task variable, testing edge case exceptions.
* POST runtime/tasks/{taskId}/variables
*/
public void testCreateSingleTaskVariableEdgeCases() throws Exception {
try {
// Test adding variable to unexisting task
ObjectNode requestNode = objectMapper.createObjectNode();
requestNode.put("name", "existingVariable");
requestNode.put("value", "simple string value");
requestNode.put("scope", "local");
requestNode.put("type", "string");
ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLES_COLLECTION, "unexisting"));
try {
client.post(requestNode);
fail("Exception expected");
} catch (ResourceException expected) {
assertEquals(Status.CLIENT_ERROR_NOT_FOUND, expected.getStatus());
assertEquals("task unexisting doesn't exist", expected.getStatus().getDescription());
}
// Test trying to create already existing variable
Task task = taskService.newTask();
taskService.saveTask(task);
taskService.setVariable(task.getId(), "existingVariable", "Value 1");
client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLES_COLLECTION, task.getId()));
try {
client.post(requestNode);
fail("Exception expected");
} catch (ResourceException expected) {
assertEquals(Status.CLIENT_ERROR_CONFLICT, expected.getStatus());
assertEquals("Variable 'existingVariable' is already present on task '" + task.getId() + "'.", expected.getStatus().getDescription());
}
// Test setting global variable on standalone task
requestNode.put("name", "myVariable");
requestNode.put("value", "simple string value");
requestNode.put("scope", "global");
requestNode.put("type", "string");
client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLES_COLLECTION, task.getId()));
try {
client.post(requestNode);
fail("Exception expected");
} catch (ResourceException expected) {
assertEquals(Status.CLIENT_ERROR_BAD_REQUEST, expected.getStatus());
assertEquals("Cannot set global variable 'myVariable' on task '" + task.getId() + "', task is not part of process.", expected.getStatus()
.getDescription());
}
// Test creating nameless variable
requestNode = objectMapper.createObjectNode();
requestNode.put("value", "simple string value");
client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLES_COLLECTION, task.getId()));
try {
client.post(requestNode);
fail("Exception expected");
} catch (ResourceException expected) {
assertEquals(Status.CLIENT_ERROR_BAD_REQUEST, expected.getStatus());
assertEquals("Variable name is required", expected.getStatus().getDescription());
}
} finally {
// Clean adhoc-tasks even if test fails
List<Task> tasks = taskService.createTaskQuery().list();
for (Task task : tasks) {
taskService.deleteTask(task.getId(), true);
}
}
}
/**
* Test creating a single task variable, testing default types when omitted.
* POST runtime/tasks/{taskId}/variables
*/
public void testCreateSingleTaskVariableDefaultTypes() throws Exception {
try {
Task task = taskService.newTask();
taskService.saveTask(task);
ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLES_COLLECTION, task.getId()));
// String type detection
ObjectNode requestNode = objectMapper.createObjectNode();
requestNode.put("name", "stringVar");
requestNode.put("value", "String value");
requestNode.put("scope", "local");
client.post(requestNode);
assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus());
assertEquals("String value", taskService.getVariable(task.getId(), "stringVar"));
client.release();
// Integer type detection
requestNode.put("name", "integerVar");
requestNode.put("value", 123);
requestNode.put("scope", "local");
client.post(requestNode);
assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus());
assertEquals(123, taskService.getVariable(task.getId(), "integerVar"));
client.release();
// Double type detection
requestNode.put("name", "doubleVar");
requestNode.put("value", 123.456);
requestNode.put("scope", "local");
client.post(requestNode);
assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus());
assertEquals(123.456, taskService.getVariable(task.getId(), "doubleVar"));
client.release();
// Boolean type detection
requestNode.put("name", "booleanVar");
requestNode.put("value", Boolean.TRUE);
requestNode.put("scope", "local");
client.post(requestNode);
assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus());
assertEquals(Boolean.TRUE, taskService.getVariable(task.getId(), "booleanVar"));
client.release();
} finally {
// Clean adhoc-tasks even if test fails
List<Task> tasks = taskService.createTaskQuery().list();
for (Task task : tasks) {
taskService.deleteTask(task.getId(), true);
}
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="OneTaskCategory">
<process id="oneTaskProcess" name="The One Task Process">
<documentation>One task process description</documentation>
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="processTask" />
<userTask id="processTask" name="Process task" activiti:candidateUsers="kermit" activiti:candidateGroups="sales">
<documentation>Process task description</documentation>
</userTask>
<sequenceFlow id="flow2" sourceRef="processTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
\ No newline at end of file
......@@ -283,7 +283,7 @@
</para>
</section>
<section id="restVariables">
<section id="restVariables">
<title>Variable representation</title>
<para>
When working with variables (execution/procees and task), the REST-api uses some common principles and JSON-format for both reading and writing. The JSON representation of a variable looks like this:
......@@ -2120,10 +2120,96 @@ Resolves the task delegation. The task is assigned back to the task owner (if an
</para>
<para>
<emphasis role="bold">Success response body:</emphasis>
The response body contains the binary value of the variable. When the variable is of type <literal>binary</literal>, the content-type of the response is set to <literal>application/octet-stream</literal>, regardless of the content of the variable. In case of <literal>serializable</literal>, <literal>application/x-java-serialized-object</literal> is returned.
The response body contains the binary value of the variable. When the variable is of type <literal>binary</literal>, the content-type of the response is set to <literal>application/octet-stream</literal>, regardless of the content of the variable or the request accept-type header. In case of <literal>serializable</literal>, <literal>application/x-java-serialized-object</literal> is used as content-type.
</para>
</section>
<section>
<title>Create a new variable on a task</title>
<para>
<programlisting>POST runtime/tasks/{taskId}/variables</programlisting>
</para>
<para>
<table>
<title>URL parameters</title>
<tgroup cols='3'>
<thead>
<row>
<entry>Parameter</entry>
<entry>Required</entry>
<entry>Value</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>taskId</entry>
<entry>Yes</entry>
<entry>String</entry>
<entry>The id of the task to create the new variable for.</entry>
</row>
</tbody>
</tgroup>
</table>
</para>
<para>
<emphasis role="bold">Request body for creating simple (non-binary) variables:</emphasis>
<programlisting>
{
"name" : "myTaskVariable",
"scope" : "local",
"type" : "string",
"value" : "Hello my friend"
}</programlisting>
<itemizedlist>
<listitem>
<para><literal>name</literal>: Required name of the variable</para>
</listitem>
<listitem>
<para><literal>scope</literal>: Scope of variable that is created. If omitted, <literal>local</literal> is assumed.</para>
</listitem>
<listitem>
<para><literal>type</literal>: Type of variable that is created. If omitted, reverts to raw JSON-value type (string, boolean, integer or double).</para>
</listitem>
<listitem>
<para><literal>value</literal>: Variable value.</para>
</listitem>
</itemizedlist>
More information about the variable format can be found in <link linkend="restVariables">the REST variables section</link>.
</para>
<para>
<table>
<title>Response codes</title>
<tgroup cols='2'>
<thead>
<row>
<entry>Response code</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>201</entry>
<entry>Indicates the variable was created and the result is returned.</entry>
</row>
<row>
<entry>400</entry>
<entry>Indicates the name of the variable to create was missing or that an attempt is done to create a variable on a standalone task (without a process associated) with scope <literal>global</literal>. Status message provides additional information.</entry>
</row>
<row>
<entry>404</entry>
<entry>Indicates the requested task was not found.</entry>
</row>
<row>
<entry>409</entry>
<entry>Indicates the task already has a variable with the given name. Use the PUT method to update the task variable instead.</entry>
</row>
</tbody>
</tgroup>
</table>
</para>
</section>
</section>
<!-- Legacy -->
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册