diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/task/TaskRequest.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/task/TaskRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..7359a36555b8361a430b0f3575781d806f065b9d --- /dev/null +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/task/TaskRequest.java @@ -0,0 +1,124 @@ +/* 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.api.task; + +import java.util.Date; + + +/** + * Request body containing a task and general properties. + * + * @author Frederik Heremans + */ +public class TaskRequest { + + private String owner; + private String assignee; + private String delegationState; + private String name; + private String description; + private Date dueDate; + private int priority; + private String parentTaskId; + + private boolean ownerSet = false; + private boolean assigneeSet = false; + private boolean delegationStateSet = false; + private boolean nameSet = false; + private boolean descriptionSet = false; + private boolean duedateSet = false; + private boolean prioritySet = false; + private boolean parentTaskIdSet = false; + + public String getOwner() { + return owner; + } + public void setOwner(String owner) { + this.owner = owner; + ownerSet = true; + } + public String getAssignee() { + return assignee; + } + public void setAssignee(String assignee) { + this.assignee = assignee; + assigneeSet = true; + } + public String getDelegationState() { + return delegationState; + } + public void setDelegationState(String delegationState) { + this.delegationState = delegationState; + delegationStateSet = true; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + nameSet = true; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + descriptionSet = true; + } + public Date getDueDate() { + return dueDate; + } + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + duedateSet = true; + } + public int getPriority() { + return priority; + } + public void setPriority(int priority) { + this.priority = priority; + prioritySet = true; + } + public String getParentTaskId() { + return parentTaskId; + } + public void setParentTaskId(String parentTaskId) { + this.parentTaskId = parentTaskId; + parentTaskIdSet = true; + } + public boolean isOwnerSet() { + return ownerSet; + } + public boolean isAssigneeSet() { + return assigneeSet; + } + public boolean isDelegationStateSet() { + return delegationStateSet; + } + public boolean isNameSet() { + return nameSet; + } + public boolean isDescriptionSet() { + return descriptionSet; + } + public boolean isDuedateSet() { + return duedateSet; + } + public boolean isPrioritySet() { + return prioritySet; + } + public boolean isParentTaskIdSet() { + return parentTaskIdSet; + } +} diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/task/TaskResource.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/task/TaskResource.java index d0e4c4a1fbd8135a11e3792154e354a2d064d1da..de38a39bd0c18093074a4c7ee6fd5141b9a2337a 100644 --- a/modules/activiti-rest/src/main/java/org/activiti/rest/api/task/TaskResource.java +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/task/TaskResource.java @@ -15,11 +15,16 @@ package org.activiti.rest.api.task; import org.activiti.engine.ActivitiIllegalArgumentException; import org.activiti.engine.ActivitiObjectNotFoundException; +import org.activiti.engine.task.DelegationState; import org.activiti.engine.task.Task; import org.activiti.rest.api.ActivitiUtil; import org.activiti.rest.api.SecuredResource; import org.activiti.rest.application.ActivitiRestServicesApplication; +import org.restlet.data.Form; +import org.restlet.data.Status; +import org.restlet.resource.Delete; import org.restlet.resource.Get; +import org.restlet.resource.Put; /** * @author Frederik Heremans @@ -28,6 +33,97 @@ public class TaskResource extends SecuredResource { @Get public TaskResponse getTask() { + return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory() + .createTaskReponse(this, getTaskFromRequest()); + } + +// TODO: move to collection-resource +// @Post +// public TaskResponse createTask(TaskRequest taskRequest) { +// Task task = ActivitiUtil.getTaskService().newTask(); +// +// // Populate the task +// task.setName(taskRequest.getName()); +// task.setAssignee(taskRequest.getAssignee()); +// task.setDescription(taskRequest.getDescription()); +// task.setDueDate(taskRequest.getDueDate()); +// task.setOwner(taskRequest.getOwner()); +// task.setParentTaskId(taskRequest.getParentTaskId()); +// task.setPriority(taskRequest.getPriority()); +// +// DelegationState delegationState = getDelegationState(taskRequest.getDelegationState()); +// task.setDelegationState(delegationState); +// +// return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory() +// .createTaskReponse(this, task); +// } + + + @Put + public TaskResponse updateTask(TaskRequest taskRequest) { + Task task = getTaskFromRequest(); + + // Populate the task properties based on the request + if(taskRequest.isNameSet()) { + task.setName(taskRequest.getName()); + } + if(taskRequest.isAssigneeSet()) { + task.setAssignee(taskRequest.getAssignee()); + } + if(taskRequest.isDescriptionSet()) { + task.setDescription(taskRequest.getDescription()); + } + if(taskRequest.isDuedateSet()) { + task.setDueDate(taskRequest.getDueDate()); + } + if(taskRequest.isOwnerSet()) { + task.setOwner(taskRequest.getOwner()); + } + if(taskRequest.isParentTaskIdSet()) { + task.setParentTaskId(taskRequest.getParentTaskId()); + } + if(taskRequest.isPrioritySet()) { + task.setPriority(taskRequest.getPriority()); + } + + if(taskRequest.isDelegationStateSet()) { + DelegationState delegationState = getDelegationState(taskRequest.getDelegationState()); + task.setDelegationState(delegationState); + } + + // Save the task and fetch agian, it's possible that an assignment-listener has updated + // fields after it was saved so we can't use the in-memory task + ActivitiUtil.getTaskService().saveTask(task); + task = ActivitiUtil.getTaskService().createTaskQuery().taskId(task.getId()).singleResult(); + + return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory() + .createTaskReponse(this, task); + } + + @Delete + public void deleteTask() { + Form query = getQuery(); + Boolean cascadeHistory = getQueryParameterAsBoolean("cascadeHistory", query); + String deleteReason = getQueryParameter("deleteReason", query); + + Task taskToDelete = getTaskFromRequest(); + + if(cascadeHistory != null) { + // Ignore delete-reason since the task-history (where the reason is recorded) will be deleted anyway + ActivitiUtil.getTaskService().deleteTask(taskToDelete.getId(), cascadeHistory); + } else { + // Delete with delete-reason + ActivitiUtil.getTaskService().deleteTask(taskToDelete.getId(), deleteReason); + } + + getResponse().setStatus(Status.SUCCESS_NO_CONTENT); + } + + + /** + * Get valid task from request. Throws exception if task doen't exist or if task id is not provided. + */ + protected Task getTaskFromRequest() { String taskId = getAttribute("taskId"); if (taskId == null) { @@ -38,7 +134,21 @@ public class TaskResource extends SecuredResource { if (task == null) { throw new ActivitiObjectNotFoundException("Could not find a task with id '" + taskId + "'.", Task.class); } - return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory().createTaskReponse(this, task); + return task; + } + + protected DelegationState getDelegationState(String delegationState) { + DelegationState state = null; + if(delegationState != null) { + if(DelegationState.RESOLVED.name().toLowerCase().equals(delegationState)) { + return DelegationState.RESOLVED; + } else if(DelegationState.PENDING.name().toLowerCase().equals(delegationState)) { + return DelegationState.PENDING; + } else { + throw new ActivitiIllegalArgumentException("Illegal value for delegationState: " + delegationState); + } + } + return state; } } diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/application/RestServicesInit.java b/modules/activiti-rest/src/main/java/org/activiti/rest/application/RestServicesInit.java index 897c4ba22cb3d8981064327e8d3e78b1d5b49e91..9a75ec973dd4799a11b88e86a0ce0c905f57e7b8 100644 --- a/modules/activiti-rest/src/main/java/org/activiti/rest/application/RestServicesInit.java +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/application/RestServicesInit.java @@ -75,7 +75,6 @@ public class RestServicesInit { router.attach("/runtime/tasks/{taskId}", TaskResource.class); - // Old rest-urls router.attach("/process-engine", ProcessEngineResource.class); diff --git a/modules/activiti-rest/src/test/java/org/activiti/rest/BaseRestTestCase.java b/modules/activiti-rest/src/test/java/org/activiti/rest/BaseRestTestCase.java index 2d800d35bd922202ec7eef102c4b961b79f8d644..15e55d4ecea1edd59aeef55fd3bea8f3b9396f68 100644 --- a/modules/activiti-rest/src/test/java/org/activiti/rest/BaseRestTestCase.java +++ b/modules/activiti-rest/src/test/java/org/activiti/rest/BaseRestTestCase.java @@ -372,4 +372,8 @@ public class BaseRestTestCase extends PvmTestCase { return null; } } + + protected String getISODateString(Date time) { + return ISODateTimeFormat.dateTime().print(time.getTime()); + } } diff --git a/modules/activiti-rest/src/test/java/org/activiti/rest/api/runtime/TaskResourceTest.java b/modules/activiti-rest/src/test/java/org/activiti/rest/api/runtime/TaskResourceTest.java index afebbdd2586b29db5a48dd1ee9dddd62b54e57c6..5046b287580b6906f5d686732c21c984231198aa 100644 --- a/modules/activiti-rest/src/test/java/org/activiti/rest/api/runtime/TaskResourceTest.java +++ b/modules/activiti-rest/src/test/java/org/activiti/rest/api/runtime/TaskResourceTest.java @@ -16,6 +16,7 @@ package org.activiti.rest.api.runtime; import java.util.Calendar; import java.util.List; +import org.activiti.engine.history.HistoricTaskInstance; import org.activiti.engine.impl.history.HistoryLevel; import org.activiti.engine.impl.util.ClockUtil; import org.activiti.engine.runtime.ProcessInstance; @@ -25,8 +26,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; /** @@ -54,6 +58,7 @@ public class TaskResourceTest extends BaseRestTestCase { ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, task.getId())); Representation response = client.get(); + assertEquals(Status.SUCCESS_OK, client.getResponse().getStatus()); // Check resulting task JsonNode responseNode = objectMapper.readTree(response.getStream()); @@ -103,6 +108,7 @@ public class TaskResourceTest extends BaseRestTestCase { ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, task.getId())); Representation response = client.get(); + assertEquals(Status.SUCCESS_OK, client.getResponse().getStatus()); // Check resulting task JsonNode responseNode = objectMapper.readTree(response.getStream()); @@ -127,11 +133,223 @@ public class TaskResourceTest extends BaseRestTestCase { // Clean adhoc-tasks even if test fails List tasks = taskService.createTaskQuery().list(); for(Task task : tasks) { - taskService.deleteTask(task.getId()); - if(processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) { - historyService.deleteHistoricTaskInstance(task.getId()); - } + taskService.deleteTask(task.getId(), true); } } } + + /** + * Test updating a single task. + * PUT runtime/tasks/{taskId} + */ + public void testUpdateTask() throws Exception { + try { + Calendar now = Calendar.getInstance(); + Task parentTask = taskService.newTask(); + taskService.saveTask(parentTask); + + Task task = taskService.newTask(); + task.setParentTaskId(parentTask.getId()); + task.setName("Task name"); + task.setDescription("Description"); + task.setAssignee("kermit"); + task.setDelegationState(DelegationState.RESOLVED); + task.setDescription("Description"); + task.setDueDate(now.getTime()); + task.setOwner("owner"); + task.setPriority(20); + taskService.saveTask(task); + + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, task.getId())); + ObjectNode requestNode = objectMapper.createObjectNode(); + + // Execute the request with an empty request JSON-object + client.put(requestNode); + assertEquals(Status.SUCCESS_OK, client.getResponse().getStatus()); + + task = taskService.createTaskQuery().taskId(task.getId()).singleResult(); + assertEquals("Task name", task.getName()); + assertEquals("Description", task.getDescription()); + assertEquals("kermit", task.getAssignee()); + assertEquals("owner", task.getOwner()); + assertEquals(20, task.getPriority()); + assertEquals(DelegationState.RESOLVED, task.getDelegationState()); + assertEquals(now.getTime(), task.getDueDate()); + assertEquals(parentTask.getId(), task.getParentTaskId()); + + + } finally { + // Clean adhoc-tasks even if test fails + List tasks = taskService.createTaskQuery().list(); + for(Task task : tasks) { + taskService.deleteTask(task.getId(), true); + } + } + } + + /** + * Test updating a single task without passing in any value, no values should be altered. + * PUT runtime/tasks/{taskId} + */ + public void testUpdateTaskNoOverrides() throws Exception { + try { + Task task = taskService.newTask(); + taskService.saveTask(task); + + Task parentTask = taskService.newTask(); + taskService.saveTask(parentTask); + + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, task.getId())); + ObjectNode requestNode = objectMapper.createObjectNode(); + + Calendar dueDate = Calendar.getInstance(); + String dueDateString = getISODateString(dueDate.getTime()); + + requestNode.put("name", "New task name"); + requestNode.put("description", "New task description"); + requestNode.put("assignee", "assignee"); + requestNode.put("owner", "owner"); + requestNode.put("priority", 20); + requestNode.put("delegationState", "resolved"); + requestNode.put("dueDate", dueDateString); + requestNode.put("parentTaskId", parentTask.getId()); + + // Execute the request + client.put(requestNode); + assertEquals(Status.SUCCESS_OK, client.getResponse().getStatus()); + + + task = taskService.createTaskQuery().taskId(task.getId()).singleResult(); + assertEquals("New task name", task.getName()); + assertEquals("New task description", task.getDescription()); + assertEquals("assignee", task.getAssignee()); + assertEquals("owner", task.getOwner()); + assertEquals(20, task.getPriority()); + assertEquals(DelegationState.RESOLVED, task.getDelegationState()); + assertEquals(dueDate.getTime(), task.getDueDate()); + assertEquals(parentTask.getId(), task.getParentTaskId()); + + + } finally { + // Clean adhoc-tasks even if test fails + List tasks = taskService.createTaskQuery().list(); + for(Task task : tasks) { + taskService.deleteTask(task.getId(), true); + } + } + } + + /** + * Test updating an unexisting task. + * PUT runtime/tasks/{taskId} + */ + public void testUpdateUnexistingTask() throws Exception { + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, "unexistingtask")); + ObjectNode requestNode = objectMapper.createObjectNode(); + + // Execute the request with an empty request JSON-object + try { + client.put(requestNode); + fail("Exception expected"); + } catch(ResourceException expected) { + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, expected.getStatus()); + assertEquals("Could not find a task with id 'unexistingtask'.", expected.getStatus().getDescription()); + } + } + + /** + * Test deleting a single task. + * DELETE runtime/tasks/{taskId} + */ + public void testDeleteTask() throws Exception { + try { + + // 1. Simple delete + Task task = taskService.newTask(); + taskService.saveTask(task); + String taskId = task.getId(); + + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, taskId)); + + // Execute the request + client.delete(); + assertEquals(Status.SUCCESS_NO_CONTENT, client.getResponse().getStatus()); + + task = taskService.createTaskQuery().taskId(task.getId()).singleResult(); + assertNull(task); + + if(processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.AUDIT)) { + // Check that the historic task has not been deleted + assertNotNull(historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult()); + } + + // 2. Cascade delete + task = taskService.newTask(); + taskService.saveTask(task); + taskId = task.getId(); + + client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, taskId) + "?cascadeHistory=true"); + + // Execute the request + client.delete(); + assertEquals(Status.SUCCESS_NO_CONTENT, client.getResponse().getStatus()); + + task = taskService.createTaskQuery().taskId(task.getId()).singleResult(); + assertNull(task); + + if(processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.AUDIT)) { + // Check that the historic task has been deleted + assertNull(historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult()); + } + + // 3. Delete with reason + task = taskService.newTask(); + taskService.saveTask(task); + taskId = task.getId(); + + client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, taskId) + "?deleteReason=fortestingpurposes"); + + // Execute the request + client.delete(); + assertEquals(Status.SUCCESS_NO_CONTENT, client.getResponse().getStatus()); + + task = taskService.createTaskQuery().taskId(task.getId()).singleResult(); + assertNull(task); + + if(processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.AUDIT)) { + // Check that the historic task has been deleted and delete-reason has been set + HistoricTaskInstance instance = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult(); + assertNotNull(instance); + assertEquals("fortestingpurposes", instance.getDeleteReason()); + } + } finally { + // Clean adhoc-tasks even if test fails + List tasks = taskService.createTaskQuery().list(); + for(Task task : tasks) { + taskService.deleteTask(task.getId(), true); + } + + // Clean historic tasks with no runtime-counterpart + List historicTasks = historyService.createHistoricTaskInstanceQuery().list(); + for(HistoricTaskInstance task : historicTasks) { + historyService.deleteHistoricTaskInstance(task.getId()); + } + } + } + + /** + * Test updating an unexisting task. + * PUT runtime/tasks/{taskId} + */ + public void testDeleteUnexistingTask() throws Exception { + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_TASK, "unexistingtask")); + + try { + client.delete(); + fail("Exception expected"); + } catch(ResourceException expected) { + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, expected.getStatus()); + assertEquals("Could not find a task with id 'unexistingtask'.", expected.getStatus().getDescription()); + } + } }