/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.activiti.rest.service.api.runtime; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.activiti.engine.test.Deployment; import org.activiti.rest.service.BaseSpringRestTestCase; import org.activiti.rest.service.HttpMultipartHelper; import org.activiti.rest.service.api.RestUrls; import org.apache.commons.io.IOUtils; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; /** * Test for all REST-operations related to a single task variable. * * @author Frederik Heremans */ public class TaskVariableResourceTest extends BaseSpringRestTestCase { /** * Test getting a task variable. GET * runtime/tasks/{taskId}/variables/{variableName} */ @Deployment public void testGetTaskVariable() throws Exception { try { // Test variable behaviour on standalone tasks Task task = taskService.newTask(); taskService.saveTask(task); taskService.setVariableLocal(task.getId(), "localTaskVariable", "localValue"); CloseableHttpResponse response = executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, task.getId(), "localTaskVariable")), HttpStatus.SC_OK); JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); closeResponse(response); assertNotNull(responseNode); assertEquals("local", responseNode.get("scope").asText()); assertEquals("localValue", responseNode.get("value").asText()); assertEquals("localTaskVariable", responseNode.get("name").asText()); assertEquals("string", responseNode.get("type").asText()); // Test variable behaviour for a process-task ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess", Collections.singletonMap("sharedVariable", (Object) "processValue")); Task processTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); taskService.setVariableLocal(processTask.getId(), "sharedVariable", "taskValue"); // ANY scope, local should get precedence response = executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, processTask.getId(), "sharedVariable")), HttpStatus.SC_OK); responseNode = objectMapper.readTree(response.getEntity().getContent()); closeResponse(response); assertNotNull(responseNode); assertEquals("local", responseNode.get("scope").asText()); assertEquals("taskValue", responseNode.get("value").asText()); assertEquals("sharedVariable", responseNode.get("name").asText()); assertEquals("string", responseNode.get("type").asText()); // LOCAL scope response = executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, processTask.getId(), "sharedVariable") + "?scope=local"), HttpStatus.SC_OK); responseNode = objectMapper.readTree(response.getEntity().getContent()); closeResponse(response); assertNotNull(responseNode); assertEquals("local", responseNode.get("scope").asText()); assertEquals("taskValue", responseNode.get("value").asText()); assertEquals("sharedVariable", responseNode.get("name").asText()); assertEquals("string", responseNode.get("type").asText()); // GLOBAL scope response = executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, processTask.getId(), "sharedVariable") + "?scope=global"), HttpStatus.SC_OK); responseNode = objectMapper.readTree(response.getEntity().getContent()); closeResponse(response); assertNotNull(responseNode); assertEquals("global", responseNode.get("scope").asText()); assertEquals("processValue", responseNode.get("value").asText()); assertEquals("sharedVariable", responseNode.get("name").asText()); assertEquals("string", responseNode.get("type").asText()); // Illegal scope closeResponse(executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, processTask.getId(), "sharedVariable") + "?scope=illegal"), HttpStatus.SC_BAD_REQUEST)); // Unexisting task closeResponse(executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, "unexisting", "sharedVariable")), HttpStatus.SC_NOT_FOUND)); // Unexisting variable closeResponse(executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, processTask.getId(), "unexistingVariable")), HttpStatus.SC_NOT_FOUND)); } finally { // Clean adhoc-tasks even if test fails List<Task> tasks = taskService.createTaskQuery().list(); for (Task task : tasks) { if(task.getExecutionId() == null) { taskService.deleteTask(task.getId(), true); } } } } /** * Test getting a task variable. GET * runtime/tasks/{taskId}/variables/{variableName}/data */ public void testGetTaskVariableData() throws Exception { try { // Test variable behaviour on standalone tasks Task task = taskService.newTask(); taskService.saveTask(task); taskService.setVariableLocal(task.getId(), "localTaskVariable", "This is a binary piece of text".getBytes()); // Force content-type to TEXT_PLAIN to make sure this is ignored and application-octect-stream is always returned CloseableHttpResponse response = executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE_DATA, task.getId(), "localTaskVariable")), HttpStatus.SC_OK); String actualResponseBytesAsText = IOUtils.toString(response.getEntity().getContent()); closeResponse(response); assertEquals("This is a binary piece of text", actualResponseBytesAsText); assertEquals("application/octet-stream", response.getEntity().getContentType().getValue()); } finally { // Clean adhoc-tasks even if test fails List<Task> tasks = taskService.createTaskQuery().list(); for (Task task : tasks) { taskService.deleteTask(task.getId(), true); } } } /** * Test getting a task variable. GET * runtime/tasks/{taskId}/variables/{variableName}/data */ public void testGetTaskVariableDataSerializable() throws Exception { try { TestSerializableVariable originalSerializable = new TestSerializableVariable(); originalSerializable.setSomeField("This is some field"); // Test variable behaviour on standalone tasks Task task = taskService.newTask(); taskService.saveTask(task); taskService.setVariableLocal(task.getId(), "localTaskVariable", originalSerializable); CloseableHttpResponse response = executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE_DATA, task.getId(), "localTaskVariable")), HttpStatus.SC_OK); // Read the serializable from the stream ObjectInputStream stream = new ObjectInputStream(response.getEntity().getContent()); Object readSerializable = stream.readObject(); assertNotNull(readSerializable); assertTrue(readSerializable instanceof TestSerializableVariable); assertEquals("This is some field", ((TestSerializableVariable) readSerializable).getSomeField()); assertEquals("application/x-java-serialized-object", response.getEntity().getContentType().getValue()); closeResponse(response); } finally { // Clean adhoc-tasks even if test fails List<Task> tasks = taskService.createTaskQuery().list(); for (Task task : tasks) { taskService.deleteTask(task.getId(), true); } } } /** * Test getting a task variable. GET * runtime/tasks/{taskId}/variables/{variableName}/data */ public void testGetTaskVariableDataForIllegalVariables() throws Exception { try { // Test variable behaviour on standalone tasks Task task = taskService.newTask(); taskService.saveTask(task); taskService.setVariableLocal(task.getId(), "localTaskVariable", "this is a plain string variable"); // Try getting data for non-binary variable closeResponse(executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE_DATA, task.getId(), "localTaskVariable")), HttpStatus.SC_NOT_FOUND)); // Try getting data for unexisting property closeResponse(executeRequest(new HttpGet(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE_DATA, task.getId(), "unexistingVariable")), HttpStatus.SC_NOT_FOUND)); } finally { // Clean adhoc-tasks even if test fails List<Task> tasks = taskService.createTaskQuery().list(); for (Task task : tasks) { taskService.deleteTask(task.getId(), true); } } } /** * Test deleting a single task variable in all scopes, including "not found" check. * * DELETE runtime/tasks/{taskId}/variables/{variableName} */ @Deployment public void testDeleteTaskVariable() throws Exception { ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess", Collections.singletonMap("overlappingVariable", (Object) "processValue")); Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); taskService.setVariableLocal(task.getId(), "overlappingVariable", "taskValue"); taskService.setVariableLocal(task.getId(), "anotherTaskVariable", "taskValue"); // Delete variable without scope, local should be presumed -> local removed and global should be retained HttpDelete httpDelete = new HttpDelete(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, task.getId(), "overlappingVariable")); closeResponse(executeRequest(httpDelete, HttpStatus.SC_NO_CONTENT)); assertFalse(taskService.hasVariableLocal(task.getId(), "overlappingVariable")); assertTrue(taskService.hasVariable(task.getId(), "overlappingVariable")); // Delete local scope variable httpDelete = new HttpDelete(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, task.getId(), "anotherTaskVariable") + "?scope=local"); closeResponse(executeRequest(httpDelete, HttpStatus.SC_NO_CONTENT)); assertFalse(taskService.hasVariableLocal(task.getId(), "anotherTaskVariable")); // Delete global scope variable assertTrue(taskService.hasVariable(task.getId(), "overlappingVariable")); httpDelete = new HttpDelete(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, task.getId(), "overlappingVariable") + "?scope=global"); closeResponse(executeRequest(httpDelete, HttpStatus.SC_NO_CONTENT)); assertFalse(taskService.hasVariable(task.getId(), "overlappingVariable")); // Run the same delete again, variable is not there so 404 should be returned closeResponse(executeRequest(httpDelete, HttpStatus.SC_NOT_FOUND)); } /** * Test updating a single task variable in all scopes, including "not found" check. * * PUT runtime/tasks/{taskId}/variables/{variableName} */ @Deployment public void testUpdateTaskVariable() throws Exception { ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess", Collections.singletonMap("overlappingVariable", (Object) "processValue")); Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); taskService.setVariableLocal(task.getId(), "overlappingVariable", "taskValue"); // Update variable without scope, local should be presumed -> local updated and global should be retained ObjectNode requestNode = objectMapper.createObjectNode(); requestNode.put("name", "overlappingVariable"); requestNode.put("value", "updatedLocalValue"); requestNode.put("type", "string"); HttpPut httpPut = new HttpPut(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, task.getId(), "overlappingVariable")); httpPut.setEntity(new StringEntity(requestNode.toString())); CloseableHttpResponse response = executeRequest(httpPut, HttpStatus.SC_OK); JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); closeResponse(response); assertNotNull(responseNode); assertEquals("updatedLocalValue", responseNode.get("value").asText()); assertEquals("local", responseNode.get("scope").asText()); // Check local value is changed in engine and global one remains unchanged assertEquals("updatedLocalValue", taskService.getVariableLocal(task.getId(), "overlappingVariable")); assertEquals("processValue", runtimeService.getVariable(task.getExecutionId(), "overlappingVariable")); // Update variable in local scope requestNode = objectMapper.createObjectNode(); requestNode.put("name", "overlappingVariable"); requestNode.put("value", "updatedLocalValueOnceAgain"); requestNode.put("type", "string"); requestNode.put("scope", "local"); httpPut.setEntity(new StringEntity(requestNode.toString())); response = executeRequest(httpPut, HttpStatus.SC_OK); responseNode = objectMapper.readTree(response.getEntity().getContent()); closeResponse(response); assertNotNull(responseNode); assertEquals("updatedLocalValueOnceAgain", responseNode.get("value").asText()); assertEquals("local", responseNode.get("scope").asText()); // Check local value is changed in engine and global one remains unchanged assertEquals("updatedLocalValueOnceAgain", taskService.getVariableLocal(task.getId(), "overlappingVariable")); assertEquals("processValue", runtimeService.getVariable(task.getExecutionId(), "overlappingVariable")); // Update variable in global scope requestNode = objectMapper.createObjectNode(); requestNode.put("name", "overlappingVariable"); requestNode.put("value", "updatedInGlobalScope"); requestNode.put("type", "string"); requestNode.put("scope", "global"); httpPut.setEntity(new StringEntity(requestNode.toString())); response = executeRequest(httpPut, HttpStatus.SC_OK); responseNode = objectMapper.readTree(response.getEntity().getContent()); closeResponse(response); assertNotNull(responseNode); assertEquals("updatedInGlobalScope", responseNode.get("value").asText()); assertEquals("global", responseNode.get("scope").asText()); // Check global value is changed in engine and local one remains unchanged assertEquals("updatedLocalValueOnceAgain", taskService.getVariableLocal(task.getId(), "overlappingVariable")); assertEquals("updatedInGlobalScope", runtimeService.getVariable(task.getExecutionId(), "overlappingVariable")); // Try updating with mismatch between URL and body variableName unexisting property 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_TASK_VARIABLE, task.getId(), "unexistingVariable")); httpPut.setEntity(new StringEntity(requestNode.toString())); closeResponse(executeRequest(httpPut, HttpStatus.SC_NOT_FOUND)); } /** * Test updating a single task variable using a binary stream. * PUT runtime/tasks/{taskId}/variables/{variableName} */ public void testUpdateBinaryTaskVariable() throws Exception { try { Task task = taskService.newTask(); taskService.saveTask(task); taskService.setVariable(task.getId(), "binaryVariable", "Original value".getBytes()); InputStream binaryContent = new ByteArrayInputStream("This is binary content".getBytes()); // Add name, type and scope Map<String, String> additionalFields = new HashMap<String, String>(); additionalFields.put("name", "binaryVariable"); additionalFields.put("type", "binary"); additionalFields.put("scope", "local"); // Upload a valid BPMN-file using multipart-data HttpPut httpPut = new HttpPut(SERVER_URL_PREFIX + RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE, task.getId(), "binaryVariable")); httpPut.setEntity(HttpMultipartHelper.getMultiPartEntity("value", "application/octet-stream", binaryContent, additionalFields)); CloseableHttpResponse response = executeBinaryRequest(httpPut, HttpStatus.SC_OK); JsonNode responseNode = objectMapper.readTree(response.getEntity().getContent()); closeResponse(response); assertNotNull(responseNode); assertEquals("binaryVariable", responseNode.get("name").asText()); assertTrue(responseNode.get("value").isNull()); assertEquals("local", responseNode.get("scope").asText()); assertEquals("binary", responseNode.get("type").asText()); assertNotNull(responseNode.get("valueUrl").isNull()); assertTrue(responseNode.get("valueUrl").asText().endsWith(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK_VARIABLE_DATA, task.getId(), "binaryVariable"))); // Check actual value of variable in engine Object variableValue = taskService.getVariableLocal(task.getId(), "binaryVariable"); assertNotNull(variableValue); assertTrue(variableValue instanceof byte[]); assertEquals("This is binary content", new String((byte[])variableValue)); } finally { // Clean adhoc-tasks even if test fails List<Task> tasks = taskService.createTaskQuery().list(); for (Task task : tasks) { taskService.deleteTask(task.getId(), true); } } } }